Skip to content

Commit a2f17be

Browse files
committed
Add subtype optimizations to eager literal difference
1 parent 4ad3e0a commit a2f17be

1 file changed

Lines changed: 128 additions & 87 deletions

File tree

lib/elixir/lib/module/types/descr.ex

Lines changed: 128 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2258,15 +2258,20 @@ defmodule Module.Types.Descr do
22582258
do: :bdd_bot,
22592259
else: bdd_leaf(list1, difference(last1, last2))
22602260
else
2261-
bdd_difference(bdd1, bdd2, &list_leaf_disjoint?/2)
2261+
bdd_difference(bdd1, bdd2, &list_leaf_compare/2)
22622262
end
22632263
end
22642264

22652265
defp list_difference(bdd1, bdd2),
2266-
do: bdd_difference(bdd1, bdd2, &list_leaf_disjoint?/2)
2266+
do: bdd_difference(bdd1, bdd2, &list_leaf_compare/2)
22672267

2268-
defp list_leaf_disjoint?(bdd_leaf(list1, last1), bdd_leaf(list2, last2)),
2269-
do: disjoint?(list1, list2) or disjoint?(last1, last2)
2268+
defp list_leaf_compare(bdd_leaf(list1, last1), bdd_leaf(list2, last2)) do
2269+
if disjoint?(list1, list2) or disjoint?(last1, last2) do
2270+
:disjoint
2271+
else
2272+
:none
2273+
end
2274+
end
22702275

22712276
defp list_empty?(@non_empty_list_top), do: false
22722277

@@ -2969,13 +2974,13 @@ defmodule Module.Types.Descr do
29692974
end
29702975

29712976
_ when is_atom(tag) and is_atom(neg_tag) ->
2972-
case map_difference_strategy(fields, neg_fields, tag, neg_tag, :all_equal) do
2973-
:all_equal when tag == neg_tag or neg_tag == :open ->
2974-
:bdd_bot
2975-
2977+
case map_difference_strategy(fields, neg_fields, tag, neg_tag) do
29762978
:disjoint ->
29772979
bdd_leaf(tag, fields)
29782980

2981+
:left_subtype_of_right ->
2982+
:bdd_bot
2983+
29792984
{:one_key_difference, key, v1, v2} ->
29802985
t_diff = difference(fields_get(fields, key, v1), v2)
29812986

@@ -2985,38 +2990,28 @@ defmodule Module.Types.Descr do
29852990
bdd_leaf(tag, fields_store(key, t_diff, fields))
29862991
end
29872992

2988-
:left_subtype_of_right ->
2989-
:bdd_bot
2990-
29912993
_ ->
29922994
bdd_difference(map1, map2)
29932995
end
29942996

29952997
_ ->
2996-
bdd_difference(map1, map2, &map_leaf_disjoint?/2)
2998+
bdd_difference(map1, map2, &map_leaf_compare/2)
29972999
end
29983000
end
29993001

30003002
defp map_difference(bdd_leaf(:open, []), bdd2),
30013003
do: bdd_negation(bdd2)
30023004

3003-
defp map_difference(bdd1, bdd2),
3004-
do: bdd_difference(bdd1, bdd2, &map_leaf_disjoint?/2)
3005-
3006-
defp map_leaf_disjoint?(bdd_leaf(_tag1, fields1), bdd_leaf(_tag2, fields2)) do
3007-
disjoint_structs?(fields1, fields2)
3005+
defp map_difference(bdd1, bdd2) do
3006+
bdd_difference(bdd1, bdd2, &map_leaf_compare/2)
30083007
end
30093008

3010-
defp disjoint_structs?(fields1, fields2) do
3011-
case {fields_find(:__struct__, fields1), fields_find(:__struct__, fields2)} do
3012-
{{:ok, %{atom: atom} = d1}, {:ok, d2}} when map_size(d1) == 1 ->
3013-
disjoint_atom_descr?(atom, d2)
3014-
3015-
{{:ok, d1}, {:ok, %{atom: atom} = d2}} when map_size(d2) == 1 ->
3016-
disjoint_atom_descr?(atom, d1)
3017-
3018-
_ ->
3019-
false
3009+
defp map_leaf_compare(bdd_leaf(tag, fields), bdd_leaf(neg_tag, neg_fields)) do
3010+
case map_difference_strategy(fields, neg_fields, tag, neg_tag) do
3011+
:disjoint -> :disjoint
3012+
:left_subtype_of_right -> :subtype
3013+
{:one_key_difference, _, v1, v2} -> if subtype?(v1, v2), do: :subtype, else: :none
3014+
_ -> :none
30203015
end
30213016
end
30223017

@@ -4361,10 +4356,7 @@ defmodule Module.Types.Descr do
43614356
if empty_intersection? do
43624357
{acc_fields, acc_negs}
43634358
else
4364-
case map_difference_strategy(acc_fields, neg_fields, tag, neg_tag, :all_equal) do
4365-
:all_equal when tag == neg_tag or neg_tag == :open ->
4366-
{acc_fields, acc_negs}
4367-
4359+
case map_difference_strategy(acc_fields, neg_fields, tag, neg_tag) do
43684360
{:one_key_difference, key, v1, v2} ->
43694361
{fields_store(key, difference(v1, v2), acc_fields), acc_negs}
43704362

@@ -4378,13 +4370,27 @@ defmodule Module.Types.Descr do
43784370
end)
43794371
end
43804372

4381-
defp map_difference_strategy([{k1, _} | t1], [{k2, _} | _] = l2, tag1, tag2, status)
4373+
defp map_difference_strategy(fields1, fields2, tag1, tag2) do
4374+
if is_atom(tag1) and is_atom(tag2) do
4375+
status = if tag1 == tag2 or tag2 == :open, do: :all_equal, else: :none
4376+
map_difference_strategy(fields1, fields2, tag1, tag2, status)
4377+
else
4378+
:none
4379+
end
4380+
end
4381+
4382+
defp map_difference_strategy([{k1, value} | t1], [{k2, _} | _] = l2, tag1, tag2, status)
43824383
when k1 < k2 do
43834384
# Left side has a key the right side does not have,
43844385
# left can only be a subtype if the right side is open.
4386+
# If the right side is closed and the key is not optional, they are disjoint.
43854387
case status do
4386-
_ when tag2 != :open ->
4387-
:none
4388+
_ when tag2 == :closed ->
4389+
if not is_optional_static(value) do
4390+
:disjoint
4391+
else
4392+
map_difference_strategy(t1, l2, tag1, tag2, :none)
4393+
end
43884394

43894395
:all_equal ->
43904396
map_difference_strategy(t1, l2, tag1, tag2, :left_subtype_of_right)
@@ -4402,11 +4408,15 @@ defmodule Module.Types.Descr do
44024408
end
44034409
end
44044410

4405-
defp map_difference_strategy([{k1, _} | _], [{k2, value} | _], tag1, _tag2, _status)
4411+
defp map_difference_strategy([{k1, _} | _] = l1, [{k2, value} | t2], tag1, tag2, _status)
44064412
when k1 > k2 do
44074413
# Right side has a key the left side does not have,
44084414
# if left-side is closed, they are disjoint.
4409-
if tag1 == :closed and not is_optional_static(value), do: :disjoint, else: :none
4415+
if tag1 == :closed and not is_optional_static(value) do
4416+
:disjoint
4417+
else
4418+
map_difference_strategy(l1, t2, tag1, tag2, :none)
4419+
end
44104420
end
44114421

44124422
defp map_difference_strategy([{_, v} | t1], [{_, v} | t2], tag1, tag2, status) do
@@ -4430,20 +4440,19 @@ defmodule Module.Types.Descr do
44304440
:none
44314441
end
44324442

4433-
# all_equal or left_subtype_of_right
44344443
_ ->
4435-
if subtype?(v1, v2),
4444+
if status in [:all_equal, :left_subtype_of_right] and subtype?(v1, v2),
44364445
do: map_difference_strategy(t1, t2, tag1, tag2, :left_subtype_of_right),
4437-
else: :none
4446+
else: map_difference_strategy(t1, t2, tag1, tag2, :none)
44384447
end
44394448
end
44404449
end
44414450

44424451
defp map_difference_strategy([], [], _tag1, _tag2, status) do
4443-
status
4452+
if status == :all_equal, do: :left_subtype_of_right, else: status
44444453
end
44454454

4446-
defp map_difference_strategy(_l1, l2, tag1, tag2, status) do
4455+
defp map_difference_strategy(l1, l2, tag1, tag2, status) do
44474456
cond do
44484457
tag2 == :open and l2 == [] ->
44494458
case status do
@@ -4455,11 +4464,17 @@ defmodule Module.Types.Descr do
44554464

44564465
:left_subtype_of_right ->
44574466
:left_subtype_of_right
4467+
4468+
:none ->
4469+
:none
44584470
end
44594471

44604472
tag1 == :closed and l2 != [] and Enum.all?(l2, fn {_, v} -> not is_optional_static(v) end) ->
44614473
:disjoint
44624474

4475+
tag2 == :closed and l1 != [] and Enum.all?(l1, fn {_, v} -> not is_optional_static(v) end) ->
4476+
:disjoint
4477+
44634478
true ->
44644479
:none
44654480
end
@@ -4816,11 +4831,15 @@ defmodule Module.Types.Descr do
48164831
do: bdd_negation(bdd2)
48174832

48184833
defp tuple_difference(bdd1, bdd2),
4819-
do: bdd_difference(bdd1, bdd2, &tuple_leaf_disjoint?/2)
4834+
do: bdd_difference(bdd1, bdd2, &tuple_leaf_compare?/2)
48204835

4821-
defp tuple_leaf_disjoint?(bdd_leaf(tag1, elements1), bdd_leaf(tag2, elements2)) do
4822-
mismatched_tuple_sizes?(tag1, elements1, tag2, elements2) or
4823-
disjoint_tagged_tuples?(elements1, elements2)
4836+
defp tuple_leaf_compare?(bdd_leaf(tag1, elements1), bdd_leaf(tag2, elements2)) do
4837+
if mismatched_tuple_sizes?(tag1, elements1, tag2, elements2) or
4838+
disjoint_tagged_tuples?(elements1, elements2) do
4839+
:disjoint
4840+
else
4841+
:none
4842+
end
48244843
end
48254844

48264845
# A very cheap check for tagged tuples
@@ -5593,65 +5612,87 @@ defmodule Module.Types.Descr do
55935612
defp bdd_difference_union(i, u1, u2),
55945613
do: bdd_difference(i, bdd_union(u1, u2))
55955614

5596-
# Optimize differences
5597-
#
5598-
# ## When D2 == :bottom
5599-
#
5600-
# We can rewrite the BDD format:
5615+
## Optimize differences
5616+
5617+
# For the right-side being a leaf, we have:
56015618
#
5602-
# B1 and not B2
5603-
# ((a1 and C1) or B1_no_C1) and not B2
5604-
# ((a1 and C1) and not B2) or (B1_no_C1 and not B2)
5605-
# (a1 and C1 and not ((a2 and C2) or U2)) or (B1_no_C1 and not B2)
5606-
# (a1 and C1 and not (a2 and C2) and not U2) or (B1_no_C1 and not B2)
5607-
# (a1 and C1 and not U2) or (B1_no_C1 and not B2)
5608-
# (a1 and C1 and not U2) or (U1 and not B2) or (not a1 and D1 and not B2)
5619+
# ((a1 and C1) or U1 or (not a1 and D1)) and not a2
56095620
#
5610-
# The last line is equivalent to the BDD:
5611-
# {a1, C1 and not U2, U1 and not B2, D1 and not B2}
5621+
# If disjoint?(a1, a2), we end up with:
56125622
#
5613-
# ## When D2 != :bottom
5623+
# (a1 and C1) or (U1 and not d2) or (not a1 and D1 and not a2)
56145624
#
5615-
# We could rewrite it to use the same optimization as bdd_leaf_intersection.
5616-
# However, given differences of negations are not common and because
5617-
# bdd_leaf_intersection can be expensive for open maps, we skip this step for now.
5625+
# If subtype?(a1, a2), we end up with:
56185626
#
5619-
# (B1) and not (B2)
5620-
# (B1) and not (B2_no_D2 or (not a2 and D2))
5621-
# (B1) and (not B2_no_D2 and (a2 or not D2))
5622-
# (B1) and (a2 or not D2) and not B2_no_D2
5623-
# ((B1 and a2) or (B1 and not D2)) and not B2_no_D2
5627+
# (U1 and not d2) or (D1 and not a2)
5628+
defp bdd_difference({a1, c1, u1, d1} = bdd1, bdd_leaf(_, _) = bdd2, leaf_compare)
5629+
when is_tuple(bdd2) do
5630+
case leaf_compare.(a1, bdd2) do
5631+
:disjoint ->
5632+
bdd_union(
5633+
bdd_difference(u1, bdd2, leaf_compare),
5634+
bdd_difference({a1, :bdd_bot, :bdd_bot, d1}, bdd2)
5635+
)
5636+
|> bdd_union({a1, c1, :bdd_bot, :bdd_bot})
56245637

5625-
defp bdd_difference(bdd1, {_, _, _, d} = bdd2, _leaf_disjoint) when d != :bdd_bot do
5626-
bdd_difference(bdd1, bdd2)
5627-
end
5638+
:subtype ->
5639+
bdd_union(bdd_difference(u1, bdd2, leaf_compare), bdd_difference(d1, bdd2, leaf_compare))
56285640

5629-
defp bdd_difference(bdd_leaf(_, _) = bdd1, bdd2, leaf_disjoint)
5630-
when is_tuple(bdd2) do
5631-
{leaf, _, u, :bdd_bot} = bdd_expand(bdd2)
5641+
:none when a1 < bdd2 ->
5642+
{a1, bdd_difference(c1, bdd2, leaf_compare), bdd_difference(u1, bdd2, leaf_compare),
5643+
bdd_difference(d1, bdd2, leaf_compare)}
5644+
|> case do
5645+
{_, :bdd_bot, u, :bdd_bot} -> u
5646+
other -> other
5647+
end
56325648

5633-
case leaf_disjoint.(bdd1, leaf) do
5634-
true when u == :bdd_bot -> bdd1
5635-
true -> bdd_difference(bdd1, u, leaf_disjoint)
5636-
false -> bdd_difference(bdd1, bdd2)
5649+
:none ->
5650+
bdd_difference(bdd1, bdd2)
56375651
end
56385652
end
56395653

5640-
defp bdd_difference({a1, c1, u1, d1} = bdd1, bdd2, leaf_disjoint)
5641-
when is_tuple(bdd2) do
5642-
{a2, _c2, u2, :bdd_bot} = bdd_expand(bdd2)
5654+
# For the left-side being a leaf, we have:
5655+
#
5656+
# a1 and not ((a2 and C2) or U2 or (not a2 and D2))
5657+
# a1 and not (a2 and C2) and not U2 and (a2 or not D2)
5658+
#
5659+
# If disjoint?(a1, a2):
5660+
#
5661+
# a1 and not (a2 and C2) = a1 (a2 and C2 is subset of a2, disjoint from a1)
5662+
# a1 and (a2 or not D2) = a1 and not D2 (a1 and a2 = bottom)
5663+
#
5664+
# Result: a1 and not D2 and not U2
5665+
#
5666+
# If subtype?(a1, a2):
5667+
#
5668+
# a1 and not (a2 and C2) = a1 and not C2 (a1 and not a2 = bottom)
5669+
# a1 and (a2 or not D2) = a1 (a1 and a2 = a1)
5670+
#
5671+
# Result: a1 and not C2 and not U2
5672+
defp bdd_difference(bdd_leaf(_, _) = bdd1, bdd2, leaf_compare) when is_tuple(bdd2) do
5673+
{a2, c2, u2, d2} = bdd_expand(bdd2)
56435674

5644-
case leaf_disjoint.(a1, a2) do
5645-
true ->
5646-
{a1, bdd_difference(c1, u2, leaf_disjoint), bdd_difference(u1, bdd2, leaf_disjoint),
5647-
bdd_difference(d1, bdd2, leaf_disjoint)}
5675+
case leaf_compare.(bdd1, a2) do
5676+
:disjoint ->
5677+
bdd1 |> bdd_difference(u2, leaf_compare) |> bdd_difference(d2, leaf_compare)
5678+
5679+
:subtype ->
5680+
bdd1 |> bdd_difference(u2, leaf_compare) |> bdd_difference(c2, leaf_compare)
5681+
5682+
:none when a2 < bdd1 ->
5683+
{a2, bdd_difference(bdd1, bdd_union(c2, u2), leaf_compare), :bdd_bot,
5684+
bdd_difference(bdd1, bdd_union(d2, u2), leaf_compare)}
5685+
|> case do
5686+
{_, :bdd_bot, u, :bdd_bot} -> u
5687+
other -> other
5688+
end
56485689

5649-
false ->
5690+
:none ->
56505691
bdd_difference(bdd1, bdd2)
56515692
end
56525693
end
56535694

5654-
defp bdd_difference(bdd1, bdd2, _leaf_disjoint) do
5695+
defp bdd_difference(bdd1, bdd2, _leaf_compare) do
56555696
bdd_difference(bdd1, bdd2)
56565697
end
56575698

0 commit comments

Comments
 (0)