-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Using lazy BDD structure for functions #14799
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from 2 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
760552c
Unify fun BDD ops with the rest
gldubc a883888
Add lazy bdd for funs. Solves spitfire perf issue.
gldubc 580fb13
Do not require ordering when matching statics and dynamics
josevalim 8cd653e
Fix fun_top?
josevalim d57266b
Eliminate fun unions
josevalim 4a29c67
Remove fun_eliminate_intersections
josevalim a58f713
Refactor bdd creation
gldubc 3ab4ee1
Prepare for lazy BDD switch
gldubc File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -27,6 +27,7 @@ defmodule Module.Types.Descr do | |||||||||
@bit_top (1 <<< 7) - 1 | ||||||||||
@bit_number @bit_integer ||| @bit_float | ||||||||||
|
||||||||||
# Remark: those use AST for BDDs | ||||||||||
defmacrop map_literal(tag, fields), do: {:{}, [], [{tag, fields}, :bdd_top, :bdd_bot]} | ||||||||||
defmacrop tuple_literal(tag, elements), do: {:{}, [], [{tag, elements}, :bdd_top, :bdd_bot]} | ||||||||||
defmacrop list_literal(list, last), do: {:{}, [], [{list, last}, :bdd_top, :bdd_bot]} | ||||||||||
|
@@ -48,6 +49,7 @@ defmodule Module.Types.Descr do | |||||||||
{:domain_key, :list} | ||||||||||
] | ||||||||||
|
||||||||||
# Remark: those are explicit BDD constructors. The functional constructors are `bdd_new/1` and `bdd_new/3`. | ||||||||||
@fun_top :bdd_top | ||||||||||
@atom_top {:negation, :sets.new(version: 2)} | ||||||||||
@map_top {{:open, %{}}, :bdd_top, :bdd_bot} | ||||||||||
|
@@ -1090,7 +1092,7 @@ defmodule Module.Types.Descr do | |||||||||
# rather than unary functions with tuple domains to handle special cases like representing | ||||||||||
# functions of a specific arity (e.g., (none,none->term) for arity 2). | ||||||||||
# NOTE: this is a ternary (lazy) BDD where the middle node encodes unions. | ||||||||||
defp fun_new(inputs, output), do: {{inputs, output}, :bdd_top, :bdd_bot, :bdd_bot} | ||||||||||
defp fun_new(inputs, output), do: lazy_bdd_new({inputs, output}) | ||||||||||
|
||||||||||
# Creates a function type from a list of inputs and an output | ||||||||||
# where the inputs and/or output may be dynamic. | ||||||||||
|
@@ -1847,34 +1849,34 @@ defmodule Module.Types.Descr do | |||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
defp list_new(list_type, last_type) do | ||||||||||
{{list_type, last_type}, :bdd_top, :bdd_bot} | ||||||||||
defp list_new(list_type, last_type), do: bdd_new({list_type, last_type}) | ||||||||||
|
||||||||||
defp non_empty_list_literals_intersection(list_literals) do | ||||||||||
try do | ||||||||||
Enum.reduce(list_literals, {:term, :term}, fn {next_list, next_last}, {list, last} -> | ||||||||||
{non_empty_intersection!(list, next_list), non_empty_intersection!(last, next_last)} | ||||||||||
end) | ||||||||||
catch | ||||||||||
:empty -> :empty | ||||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
# Takes all the lines from the root to the leaves finishing with a 1, | ||||||||||
# and compile into tuples of positive and negative nodes. Positive nodes are | ||||||||||
# those followed by a left path, negative nodes are those followed by a right path. | ||||||||||
defp list_bdd_to_dnf(bdd), do: list_bdd_to_dnf([], {:term, :term}, [], bdd) | ||||||||||
|
||||||||||
defp list_bdd_to_dnf(acc, {list_acc, tail_acc} = pos, negs, bdd) do | ||||||||||
case bdd do | ||||||||||
:bdd_bot -> | ||||||||||
acc | ||||||||||
|
||||||||||
:bdd_top -> | ||||||||||
if list_line_empty?(list_acc, tail_acc, negs), do: acc, else: [{pos, negs} | acc] | ||||||||||
|
||||||||||
{{list, tail} = next_list, left, right} -> | ||||||||||
try do | ||||||||||
li = non_empty_intersection!(list_acc, list) | ||||||||||
la = non_empty_intersection!(tail_acc, tail) | ||||||||||
defp list_bdd_to_dnf(bdd) do | ||||||||||
bdd_to_dnf(bdd) | ||||||||||
|> Enum.reduce([], fn {pos_list, neg_list}, acc -> | ||||||||||
case non_empty_list_literals_intersection(pos_list) do | ||||||||||
:empty -> | ||||||||||
acc | ||||||||||
|
||||||||||
list_bdd_to_dnf(acc, {li, la}, negs, left) | ||||||||||
|> list_bdd_to_dnf(pos, [next_list | negs], right) | ||||||||||
catch | ||||||||||
:empty -> list_bdd_to_dnf(acc, pos, [next_list | negs], right) | ||||||||||
end | ||||||||||
end | ||||||||||
{list, last} -> | ||||||||||
if list_line_empty?(list, last, neg_list), | ||||||||||
do: acc, | ||||||||||
else: [{{list, last}, neg_list} | acc] | ||||||||||
end | ||||||||||
end) | ||||||||||
end | ||||||||||
|
||||||||||
# Takes all the lines from the root to the leaves finishing with a 1, | ||||||||||
|
@@ -1884,40 +1886,51 @@ defmodule Module.Types.Descr do | |||||||||
# if the negative list type is a supertype of the positive list type. In that case, | ||||||||||
# we can remove the negative last type from the positive one. | ||||||||||
# (If this subtracted type was empty, the whole type would be empty) | ||||||||||
defp list_bdd_to_pos_dnf(bdd), do: list_bdd_to_pos_dnf(:term, :term, bdd, []) | ||||||||||
defp list_bdd_to_pos_dnf(bdd) do | ||||||||||
bdd_to_dnf(bdd) | ||||||||||
|> Enum.reduce([], fn {pos_list, neg_list}, acc -> | ||||||||||
case non_empty_list_literals_intersection(pos_list) do | ||||||||||
:empty -> | ||||||||||
acc | ||||||||||
|
||||||||||
defp list_bdd_to_pos_dnf(list_acc, last_acc, bdd, lines_acc) do | ||||||||||
case bdd do | ||||||||||
:bdd_bot -> | ||||||||||
lines_acc | ||||||||||
{list, last} -> | ||||||||||
if list_line_empty?(list, last, neg_list), do: acc, else: [{list, last} | acc] | ||||||||||
end | ||||||||||
end) | ||||||||||
end | ||||||||||
|
||||||||||
:bdd_top -> | ||||||||||
[{list_acc, last_acc} | lines_acc] | ||||||||||
# defp list_bdd_to_pos_dnf(list_acc, last_acc, bdd, lines_acc) do | ||||||||||
# case bdd do | ||||||||||
# :bdd_bot -> | ||||||||||
# lines_acc | ||||||||||
|
||||||||||
{{list, last}, left, right} -> | ||||||||||
# Case 1: count the list_type negatively. Check condition when it affects the positive one. | ||||||||||
lines_acc = | ||||||||||
if subtype?(list_acc, list) do | ||||||||||
last = difference(last_acc, last) | ||||||||||
# :bdd_top -> | ||||||||||
# [{list_acc, last_acc} | lines_acc] | ||||||||||
|
||||||||||
if empty?(last), | ||||||||||
do: lines_acc, | ||||||||||
else: list_bdd_to_pos_dnf(list_acc, last, right, lines_acc) | ||||||||||
else | ||||||||||
list_bdd_to_pos_dnf(list_acc, last_acc, right, lines_acc) | ||||||||||
end | ||||||||||
# {{list, last}, left, right} -> | ||||||||||
# # Case 1: count the list_type negatively. Check condition when it affects the positive one. | ||||||||||
# lines_acc = | ||||||||||
# if subtype?(list_acc, list) do | ||||||||||
# last = difference(last_acc, last) | ||||||||||
|
||||||||||
# Case 2: count the list_type positively. | ||||||||||
list_acc = intersection(list_acc, list) | ||||||||||
last_acc = intersection(last_acc, last) | ||||||||||
# if empty?(last), | ||||||||||
# do: lines_acc, | ||||||||||
# else: list_bdd_to_pos_dnf(list_acc, last, right, lines_acc) | ||||||||||
# else | ||||||||||
# list_bdd_to_pos_dnf(list_acc, last_acc, right, lines_acc) | ||||||||||
# end | ||||||||||
|
||||||||||
if empty?(list_acc) or empty?(last_acc) do | ||||||||||
lines_acc | ||||||||||
else | ||||||||||
list_bdd_to_pos_dnf(list_acc, last_acc, left, lines_acc) | ||||||||||
end | ||||||||||
end | ||||||||||
end | ||||||||||
# # Case 2: count the list_type positively. | ||||||||||
# list_acc = intersection(list_acc, list) | ||||||||||
# last_acc = intersection(last_acc, last) | ||||||||||
|
||||||||||
# if empty?(list_acc) or empty?(last_acc) do | ||||||||||
# lines_acc | ||||||||||
# else | ||||||||||
# list_bdd_to_pos_dnf(list_acc, last_acc, left, lines_acc) | ||||||||||
# end | ||||||||||
# end | ||||||||||
# end | ||||||||||
|
||||||||||
defp list_pop_dynamic(:term), do: {false, :term} | ||||||||||
|
||||||||||
|
@@ -1934,10 +1947,7 @@ defmodule Module.Types.Descr do | |||||||||
@compile {:inline, list_union: 2} | ||||||||||
defp list_union(bdd1, bdd2), do: bdd_union(bdd1, bdd2) | ||||||||||
|
||||||||||
defp is_list_top?({{list, tail}, :bdd_top, :bdd_bot}) do | ||||||||||
list == :term and tail == :term | ||||||||||
end | ||||||||||
|
||||||||||
defp is_list_top?(list_literal(list, tail)), do: list == :term and tail == :term | ||||||||||
defp is_list_top?(_), do: false | ||||||||||
|
||||||||||
defp list_intersection(list_literal(list1, last1), list_literal(list2, last2)) do | ||||||||||
|
@@ -1988,16 +1998,6 @@ defmodule Module.Types.Descr do | |||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
defp non_empty_list_literals_intersection(list_literals) do | ||||||||||
try do | ||||||||||
Enum.reduce(list_literals, {:term, :term}, fn {next_list, next_last}, {list, last} -> | ||||||||||
{non_empty_intersection!(list, next_list), non_empty_intersection!(last, next_last)} | ||||||||||
end) | ||||||||||
catch | ||||||||||
:empty -> :empty | ||||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
defp list_empty?(@non_empty_list_top), do: false | ||||||||||
|
||||||||||
defp list_empty?(bdd) do | ||||||||||
|
@@ -2425,7 +2425,7 @@ 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: {{tag, fields}, :bdd_top, :bdd_bot} | ||||||||||
defp map_new(tag, fields = %{}), do: bdd_new({tag, fields}) | ||||||||||
|
||||||||||
defp map_only?(descr), do: empty?(Map.delete(descr, :map)) | ||||||||||
|
||||||||||
|
@@ -2443,12 +2443,13 @@ defmodule Module.Types.Descr do | |||||||||
|
||||||||||
nil -> | ||||||||||
case {{tag1, fields1}, {tag2, fields2}} do | ||||||||||
{r, l} when l < r -> {l, :bdd_top, {r, :bdd_top, :bdd_bot}} | ||||||||||
{l, r} -> {l, :bdd_top, {r, :bdd_top, :bdd_bot}} | ||||||||||
{r, l} when l < r -> bdd_new(l, :bdd_top, bdd_new(r)) | ||||||||||
{l, r} -> bdd_new(l, :bdd_top, bdd_new(r)) | ||||||||||
end | ||||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
@compile {:inline, map_union: 2} | ||||||||||
def map_union(bdd1, bdd2), do: bdd_union(bdd1, bdd2) | ||||||||||
|
||||||||||
defp maybe_optimize_map_union({tag1, pos1, []} = map1, {tag2, pos2, []} = map2) do | ||||||||||
|
@@ -2549,8 +2550,8 @@ defmodule Module.Types.Descr do | |||||||||
|
||||||||||
defp map_intersection(map_literal(tag1, fields1), map_literal(tag2, fields2)) do | ||||||||||
try do | ||||||||||
map = map_literal_intersection(tag1, fields1, tag2, fields2) | ||||||||||
{map, :bdd_top, :bdd_bot} | ||||||||||
map_literal = map_literal_intersection(tag1, fields1, tag2, fields2) | ||||||||||
bdd_new(map_literal) | ||||||||||
catch | ||||||||||
:empty -> :bdd_bot | ||||||||||
end | ||||||||||
|
@@ -2762,7 +2763,7 @@ defmodule Module.Types.Descr do | |||||||||
|
||||||||||
# Optimization: if the key does not exist in the map, avoid building | ||||||||||
# if_set/not_set pairs and return the popped value directly. | ||||||||||
defp map_fetch_static(%{map: {{tag_or_domains, fields}, :bdd_top, :bdd_bot}}, key) | ||||||||||
defp map_fetch_static(%{map: map_literal(tag_or_domains, fields)}, key) | ||||||||||
when not is_map_key(fields, key) do | ||||||||||
map_key_tag_to_type(tag_or_domains) |> pop_optional_static() | ||||||||||
end | ||||||||||
|
@@ -2936,8 +2937,8 @@ defmodule Module.Types.Descr do | |||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
def map_refresh_domain(%{map: {{tag, fields}, :bdd_top, :bdd_bot}}, domain, type) do | ||||||||||
%{map: {{map_refresh_tag(tag, domain, type), fields}, :bdd_top, :bdd_bot}} | ||||||||||
def map_refresh_domain(%{map: map_literal(tag, fields)}, domain, type) do | ||||||||||
%{map: bdd_new({map_refresh_tag(tag, domain, type), fields})} | ||||||||||
end | ||||||||||
|
||||||||||
def map_refresh_domain(%{map: bdd}, domain, type) do | ||||||||||
|
@@ -3305,7 +3306,7 @@ defmodule Module.Types.Descr do | |||||||||
|
||||||||||
# Takes a static map type and removes a key from it. | ||||||||||
# This allows the key to be put or deleted later on. | ||||||||||
defp map_take_static(%{map: {{tag, fields}, :bdd_top, :bdd_bot}} = descr, key, initial) | ||||||||||
defp map_take_static(%{map: map_literal(tag, fields)} = descr, key, initial) | ||||||||||
when not is_map_key(fields, key) do | ||||||||||
case tag do | ||||||||||
:open -> {true, maybe_union(initial, fn -> term() end), descr} | ||||||||||
|
@@ -4026,6 +4027,7 @@ defmodule Module.Types.Descr do | |||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
@compile {:inline, tuple_union: 2} | ||||||||||
defp tuple_union(bdd1, bdd2), do: bdd_union(bdd1, bdd2) | ||||||||||
|
||||||||||
defp maybe_optimize_tuple_union({tag1, pos1} = tuple1, {tag2, pos2} = tuple2) do | ||||||||||
|
@@ -4122,30 +4124,21 @@ defmodule Module.Types.Descr do | |||||||||
|
||||||||||
# Transforms a bdd into a union of tuples with no negations. | ||||||||||
# Note: it is important to compose the results with tuple_dnf_union/2 to avoid duplicates | ||||||||||
defp tuple_normalize(bdd), do: tuple_normalize([], {:open, []}, [], bdd) | ||||||||||
|
||||||||||
defp tuple_normalize(acc, {tag, elements} = tuple, negs, bdd) do | ||||||||||
case bdd do | ||||||||||
:bdd_bot -> | ||||||||||
acc | ||||||||||
|
||||||||||
:bdd_top -> | ||||||||||
if tuple_line_empty?(tag, elements, negs) do | ||||||||||
defp tuple_normalize(bdd) do | ||||||||||
bdd_to_dnf(bdd) | ||||||||||
|> Enum.reduce([], fn {positive_tuples, negative_tuples}, acc -> | ||||||||||
case non_empty_tuple_literals_intersection(positive_tuples) do | ||||||||||
:empty -> | ||||||||||
acc | ||||||||||
else | ||||||||||
tuple_eliminate_negations(tag, elements, negs) |> tuple_dnf_union(acc) | ||||||||||
end | ||||||||||
|
||||||||||
{{next_tag, next_elements} = next_tuple, left, right} -> | ||||||||||
# If an intersection of tuples is empty, the line is empty and we skip it. | ||||||||||
acc = | ||||||||||
case tuple_literal_intersection(tag, elements, next_tag, next_elements) do | ||||||||||
:empty -> acc | ||||||||||
new_tuple -> tuple_normalize(acc, new_tuple, negs, left) | ||||||||||
{tag, elements} -> | ||||||||||
if tuple_line_empty?(tag, elements, negative_tuples) do | ||||||||||
acc | ||||||||||
else | ||||||||||
tuple_eliminate_negations(tag, elements, negative_tuples) |> tuple_dnf_union(acc) | ||||||||||
end | ||||||||||
|
||||||||||
tuple_normalize(acc, tuple, [next_tuple | negs], right) | ||||||||||
end | ||||||||||
end | ||||||||||
end) | ||||||||||
end | ||||||||||
|
||||||||||
# Given a union of tuples, fuses the tuple unions when possible, | ||||||||||
|
@@ -4470,6 +4463,19 @@ defmodule Module.Types.Descr do | |||||||||
|
||||||||||
## BDD helpers | ||||||||||
|
||||||||||
# defp bdd_new(literal), do: lazy_bdd_new(literal) | ||||||||||
# defp bdd_new(literal, left, right), do: lazy_bdd_new(literal, left, right) | ||||||||||
|
||||||||||
# defp bdd_intersection(bdd1, bdd2), do: lazy_bdd_intersection(bdd1, bdd2) | ||||||||||
# defp bdd_difference(bdd1, bdd2), do: lazy_bdd_difference(bdd1, bdd2) | ||||||||||
# defp bdd_union(bdd1, bdd2), do: lazy_bdd_union(bdd1, bdd2) | ||||||||||
# defp bdd_negation(bdd), do: lazy_bdd_negation(bdd) | ||||||||||
# defp bdd_map(bdd, fun), do: lazy_bdd_map(bdd, fun) | ||||||||||
# defp bdd_to_dnf(bdd), do: lazy_bdd_to_dnf(bdd) | ||||||||||
|
||||||||||
defp bdd_new(literal), do: {literal, :bdd_top, :bdd_bot} | ||||||||||
defp bdd_new(literal, left, right), do: {literal, left, right} | ||||||||||
|
||||||||||
# Leaf cases | ||||||||||
defp bdd_intersection(_, :bdd_bot), do: :bdd_bot | ||||||||||
defp bdd_intersection(:bdd_bot, _), do: :bdd_bot | ||||||||||
|
@@ -4544,6 +4550,10 @@ defmodule Module.Types.Descr do | |||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
## Lazy BDD helpers | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||
defp lazy_bdd_new(literal), do: {literal, :bdd_top, :bdd_bot, :bdd_bot} | ||||||||||
defp lazy_bdd_new(literal, left, right), do: {literal, left, :bdd_bot, right} | ||||||||||
|
||||||||||
def lazy_bdd_union(bdd1, bdd2) do | ||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remember to make all of them private :) |
||||||||||
case {bdd1, bdd2} do | ||||||||||
{:bdd_top, _bdd} -> | ||||||||||
|
@@ -4650,8 +4660,23 @@ defmodule Module.Types.Descr do | |||||||||
end | ||||||||||
|
||||||||||
# Optional guard: blow up if someone passes a binary node by mistake | ||||||||||
defp lazy_bdd_to_dnf(_acc, _pos, _neg, {_lit, _t, _e}) do | ||||||||||
raise ArgumentError, "lazy_bdd_to_dnf expects lazy nodes {lit, c, u, d}" | ||||||||||
defp lazy_bdd_to_dnf(_acc, _pos, _neg, {_lit, _t, _e} = node) do | ||||||||||
raise ArgumentError, | ||||||||||
"lazy_bdd_to_dnf expects lazy nodes {lit, c, u, d} #{inspect(node)}\n | ||||||||||
#{inspect(Process.info(self(), :current_stacktrace))}" | ||||||||||
end | ||||||||||
|
||||||||||
defp lazy_bdd_map(bdd, fun) do | ||||||||||
case bdd do | ||||||||||
:bdd_bot -> | ||||||||||
:bdd_bot | ||||||||||
|
||||||||||
:bdd_top -> | ||||||||||
:bdd_top | ||||||||||
|
||||||||||
{literal, left, union, right} -> | ||||||||||
{fun.(literal), bdd_map(left, fun), bdd_map(union, fun), bdd_map(right, fun)} | ||||||||||
end | ||||||||||
end | ||||||||||
|
||||||||||
## Pairs | ||||||||||
|
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.