From 317936818fe2f8887c536a9bccdda997a584319e Mon Sep 17 00:00:00 2001 From: Guillaume Duboc <27832828+gldubc@users.noreply.github.com> Date: Thu, 14 May 2026 20:19:29 +0200 Subject: [PATCH 1/2] Order map BDD leaves by field count --- lib/elixir/lib/module/types/descr.ex | 41 +++++++++++++++++++--------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 9ad6f166c3..549e341119 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -48,10 +48,12 @@ defmodule Module.Types.Descr do # Remark: those are explicit BDD constructors. The functional constructors are `bdd_new/1` and `bdd_new/3`. @fun_top {:negation, %{}} @atom_top {:negation, :sets.new(version: 2)} - @map_top {:erlang.phash2([:open | @fields_new]), :open, @fields_new} + @map_leaf_hash_space 1 <<< 20 + @map_top {:erlang.phash2([:open | @fields_new], @map_leaf_hash_space), :open, @fields_new} @non_empty_list_top {:erlang.phash2([:term | :term]), :term, :term} @tuple_top {:erlang.phash2([:open | []]), :open, []} - @map_empty {-:erlang.phash2(@fields_new), :closed, @fields_new} + @map_empty {-:erlang.phash2([:closed | @fields_new], @map_leaf_hash_space), :closed, + @fields_new} defmacrop bdd_leaf(arg1, arg2) do quote do @@ -643,7 +645,7 @@ defmodule Module.Types.Descr do defp numberize(:map, bdd) do bdd_map(bdd, fn bdd_leaf(tag, fields) -> - bdd_leaf_new(tag, fields_map(fn _key, value -> numberize(value) end, fields)) + map_new(tag, fields_map(fn _key, value -> numberize(value) end, fields)) end) end @@ -2886,7 +2888,20 @@ defmodule Module.Types.Descr do defguardp is_optional_static(map) when is_map(map) and is_map_key(map, :optional) - defp map_new(tag, fields), do: bdd_leaf_new(tag, fields) + # Order broader map contexts before more specific ones so shared requirements + # are factored once in the BDD instead of copied under every specialized branch. + defp map_new(:closed, fields) do + {-map_leaf_order(:closed, fields), :closed, fields} + end + + defp map_new(tag, fields) do + {map_leaf_order(tag, fields), tag, fields} + end + + defp map_leaf_order(tag, fields) do + fields_size(fields) * @map_leaf_hash_space + + :erlang.phash2([tag | fields_keys(fields)], @map_leaf_hash_space) + end defp map_only?(descr), do: empty?(Map.delete(descr, :map)) @@ -2905,8 +2920,8 @@ defmodule Module.Types.Descr do defp map_union(bdd_leaf(tag1, fields1), bdd_leaf(tag2, fields2)) do case maybe_optimize_map_union(tag1, fields1, tag2, fields2) do - {tag, fields} -> bdd_leaf_new(tag, fields) - nil -> bdd_union(bdd_leaf_new(tag1, fields1), bdd_leaf_new(tag2, fields2)) + {tag, fields} -> map_new(tag, fields) + nil -> bdd_union(map_new(tag1, fields1), map_new(tag2, fields2)) end end @@ -3071,7 +3086,7 @@ defmodule Module.Types.Descr do defp map_leaf_intersection(bdd_leaf(tag1, fields1), bdd_leaf(tag2, fields2)) do try do {tag, fields} = map_literal_intersection(tag1, fields1, tag2, fields2) - bdd_leaf_new(tag, fields) + map_new(tag, fields) catch :empty -> :bdd_bot end @@ -3138,7 +3153,7 @@ defmodule Module.Types.Descr do if empty?(v_diff) do :subtype else - a_diff = bdd_leaf_new(tag, fields_store(key, v_diff, fields)) + a_diff = map_new(tag, fields_store(key, v_diff, fields)) a_type = case type do @@ -3146,14 +3161,14 @@ defmodule Module.Types.Descr do :bdd_bot :union -> - bdd_leaf_new(tag, fields_store(key, union(v1, v2), fields)) + map_new(tag, fields_store(key, union(v1, v2), fields)) :intersection -> v_int = intersection(v1, v2) if empty?(v_int), do: :bdd_bot, - else: bdd_leaf_new(tag, fields_store(key, v_int, fields)) + else: map_new(tag, fields_store(key, v_int, fields)) end {:one_key_difference, a_diff, a_type} @@ -3891,8 +3906,8 @@ defmodule Module.Types.Descr do defp map_put_key_static(%{map: bdd} = descr, key, type) do bdd = bdd_map(bdd, fn - bdd_leaf(:closed, fields) when type == @not_set -> bdd_leaf_new(:closed, fields) - bdd_leaf(tag, fields) -> bdd_leaf_new(tag, fields_store(key, type, fields)) + bdd_leaf(:closed, fields) when type == @not_set -> map_new(:closed, fields) + bdd_leaf(tag, fields) -> map_new(tag, fields_store(key, type, fields)) end) %{descr | map: bdd} @@ -4062,7 +4077,7 @@ defmodule Module.Types.Descr do defp map_update_put_domains(bdd, domain_keys, type_fun, force?) do bdd = bdd_map(bdd, fn bdd_leaf(tag, fields) -> - bdd_leaf_new(map_update_put_domain(tag, domain_keys, type_fun, force?), fields) + map_new(map_update_put_domain(tag, domain_keys, type_fun, force?), fields) end) %{map: bdd} From 6f1ac4c7d3adfbffae2f1f0fe9bfcd0c6f00dcb7 Mon Sep 17 00:00:00 2001 From: Guillaume Duboc <27832828+gldubc@users.noreply.github.com> Date: Sat, 16 May 2026 17:37:36 +0200 Subject: [PATCH 2/2] Order tuple BDD leaves by element count + tag --- lib/elixir/lib/module/types/descr.ex | 46 +++++++++++++++++++--------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/lib/elixir/lib/module/types/descr.ex b/lib/elixir/lib/module/types/descr.ex index 549e341119..d27b15fe7c 100644 --- a/lib/elixir/lib/module/types/descr.ex +++ b/lib/elixir/lib/module/types/descr.ex @@ -48,11 +48,14 @@ defmodule Module.Types.Descr do # Remark: those are explicit BDD constructors. The functional constructors are `bdd_new/1` and `bdd_new/3`. @fun_top {:negation, %{}} @atom_top {:negation, :sets.new(version: 2)} - @map_leaf_hash_space 1 <<< 20 - @map_top {:erlang.phash2([:open | @fields_new], @map_leaf_hash_space), :open, @fields_new} + # The hash range is also the stride between field-count buckets. + # Since phash2/2 returns less than the range, the count remains the primary sort key. + @map_leaf_hash_range 1 <<< 28 + @tuple_leaf_hash_range 1 <<< 28 + @map_top {:erlang.phash2([:open | @fields_new], @map_leaf_hash_range), :open, @fields_new} @non_empty_list_top {:erlang.phash2([:term | :term]), :term, :term} - @tuple_top {:erlang.phash2([:open | []]), :open, []} - @map_empty {-:erlang.phash2([:closed | @fields_new], @map_leaf_hash_space), :closed, + @tuple_top {:erlang.phash2([:open | []], @tuple_leaf_hash_range), :open, []} + @map_empty {-:erlang.phash2([:closed | @fields_new], @map_leaf_hash_range), :closed, @fields_new} defmacrop bdd_leaf(arg1, arg2) do @@ -650,7 +653,9 @@ defmodule Module.Types.Descr do end defp numberize(:tuple, bdd) do - bdd_map(bdd, fn bdd_leaf(tag, fields) -> bdd_leaf_new(tag, Enum.map(fields, &numberize/1)) end) + bdd_map(bdd, fn bdd_leaf(tag, elements) -> + tuple_new(tag, Enum.map(elements, &numberize/1)) + end) end defp numberize(:list, bdd) do @@ -2899,8 +2904,8 @@ defmodule Module.Types.Descr do end defp map_leaf_order(tag, fields) do - fields_size(fields) * @map_leaf_hash_space + - :erlang.phash2([tag | fields_keys(fields)], @map_leaf_hash_space) + fields_size(fields) * @map_leaf_hash_range + + :erlang.phash2([tag | fields_keys(fields)], @map_leaf_hash_range) end defp map_only?(descr), do: empty?(Map.delete(descr, :map)) @@ -5025,7 +5030,20 @@ defmodule Module.Types.Descr do {acc, dynamic_acc, dynamic?, static_empty?} end - defp tuple_new(tag, elements), do: bdd_leaf_new(tag, elements) + # Order broader tuple contexts before more specific ones so shared requirements + # are factored once in the BDD instead of copied under every specialized branch. + defp tuple_new(:closed, elements) do + {-tuple_leaf_order(:closed, elements), :closed, elements} + end + + defp tuple_new(tag, elements) do + {tuple_leaf_order(tag, elements), tag, elements} + end + + defp tuple_leaf_order(tag, elements) do + length(elements) * @tuple_leaf_hash_range + + :erlang.phash2([tag | elements], @tuple_leaf_hash_range) + end defp tuple_intersection(bdd_leaf(:open, []), bdd), do: bdd defp tuple_intersection(bdd, bdd_leaf(:open, [])), do: bdd @@ -5036,7 +5054,7 @@ defmodule Module.Types.Descr do defp tuple_leaf_intersection(bdd_leaf(tag1, elements1), bdd_leaf(tag2, elements2)) do case tuple_literal_intersection(tag1, elements1, tag2, elements2) do - {tag, elements} -> bdd_leaf_new(tag, elements) + {tag, elements} -> tuple_new(tag, elements) :empty -> :bdd_bot end end @@ -5306,12 +5324,12 @@ defmodule Module.Types.Descr do do: leaf defp tuple_union( - bdd_leaf(tag1, elements1) = tuple1, - bdd_leaf(tag2, elements2) = tuple2 + bdd_leaf(tag1, elements1), + bdd_leaf(tag2, elements2) ) do case maybe_optimize_tuple_union({tag1, elements1}, {tag2, elements2}) do - {tag, elements} -> bdd_leaf_new(tag, elements) - nil -> bdd_union(tuple1, tuple2) + {tag, elements} -> tuple_new(tag, elements) + nil -> bdd_union(tuple_new(tag1, elements1), tuple_new(tag2, elements2)) end end @@ -5935,7 +5953,7 @@ defmodule Module.Types.Descr do elements end - bdd_leaf_new(tag, List.insert_at(elements, index, type)) + tuple_new(tag, List.insert_at(elements, index, type)) end) end) end