Current section
Files
Jump to
Current section
Files
lib/vector/codegen.ex
defmodule Aja.Vector.CodeGen do
@moduledoc false
# Notes: averaged overhead
# Enum.sum(for i <- 1..100, do: Aja.Vector.new(1..i) |> :erts_debug.size_shared()) / 100
# 2^3 = 8 -> 87.2
# 2^4 = 16 -> 86.96
# 2^5 = 32 -> 93.56
# :array -> 77.3
# Notes: averaged over 1..1000, the trend is the opposite (:array 638.18, vec32 577.016)
@bits 4
@branch_factor :math.pow(2, @bits) |> round()
@range 1..@branch_factor
@arguments_ast Macro.generate_arguments(@branch_factor, nil)
@other_arguments_ast Macro.generate_arguments(2 * @branch_factor, nil)
|> Enum.drop(@branch_factor)
@wildcard quote do: _
defmacro bits do
@bits
end
defmacro branch_factor do
@branch_factor
end
defmacro incr_level(level) do
quote do
unquote(level) + unquote(@bits)
end
end
defmacro decr_level(level) do
quote do
unquote(level) - unquote(@bits)
end
end
defmacro radix_div(i) do
quote do
:erlang.bsr(unquote(i), unquote(@bits))
end
end
defmacro radix_rem(i) do
quote do
:erlang.band(unquote(i), unquote(@branch_factor - 1))
end
end
defmacro array_type(declaration, value) do
quote do
@type unquote(declaration) :: unquote({:{}, [], List.duplicate(value, @branch_factor)})
end
end
defmacro radix_search(index, level) do
quote do
unquote(index)
|> :erlang.bsr(unquote(level))
|> :erlang.band(unquote(@branch_factor - 1))
end
end
def range do
@range
end
def block(lines) when is_list(lines) do
{:__block__, [], lines}
end
def array() do
do_array(@arguments_ast)
end
def array(args) do
args
|> validate_args_length()
|> do_array()
end
def array_ast() do
{:{}, [], [:{}, [], @arguments_ast]}
end
def array_ast(args) do
validate_args_length(args)
{:{}, [], [:{}, [], args]}
end
def arguments do
@arguments_ast
end
def arguments(i) when i in 1..@branch_factor do
Enum.take(@arguments_ast, i)
end
def argument_at(i) when i in 0..(@branch_factor - 1) do
Enum.at(@arguments_ast, i)
end
def reversed_arguments() do
unquote(
@arguments_ast
|> Enum.reverse()
|> Macro.escape()
)
end
def reversed_arguments(i) when i in 1..@branch_factor do
@arguments_ast
|> Enum.take(i)
|> Enum.reverse()
end
def other_arguments do
@other_arguments_ast
end
def other_arguments(i) when i in 1..@branch_factor do
Enum.take(@other_arguments_ast, i)
end
def duplicate_argument(arg) do
List.duplicate(arg, @branch_factor)
end
def arguments_with_nils(i) when i in 1..@branch_factor do
nils = List.duplicate(nil, @branch_factor - i)
Enum.take(@arguments_ast, i) ++ nils
end
def arguments_with_wildcards(i) when i in 1..@branch_factor do
wilds = List.duplicate(@wildcard, @branch_factor - i)
Enum.take(@arguments_ast, i) ++ wilds
end
def arguments_with_left_wildcards(i) when i in 1..@branch_factor do
wilds = List.duplicate(@wildcard, @branch_factor - i)
wilds ++ Enum.take(@arguments_ast, i)
end
def arguments_with_complement_wildcards(i) when i in 1..@branch_factor do
wilds = List.duplicate(@wildcard, i)
Enum.drop(@arguments_ast, i) ++ wilds
end
def array_with_wildcards(n) do
n
|> arguments_with_wildcards()
|> do_array()
end
def array_with_left_wildcards(n) do
n
|> arguments_with_left_wildcards()
|> do_array()
end
def array_with_complement_wildcards(n) do
n
|> arguments_with_complement_wildcards()
|> do_array()
end
def array_with_nils(n) do
n
|> arguments_with_nils()
|> do_array()
end
def value_with_nils(value) do
[value] |> fill_with(nil)
end
def fill_with(args, value) do
missing = @branch_factor - length(args)
args ++ List.duplicate(value, missing)
end
def left_fill_with(args, value) do
missing = @branch_factor - length(args)
List.duplicate(value, missing) ++ args
end
def map_until(args \\ @arguments_ast, n, fun) when is_integer(n) and is_function(fun, 1) do
args
|> Enum.with_index()
|> Enum.map(fn
{arg, i} when i < n -> fun.(arg)
{arg, _} -> arg
end)
end
def list_with_rest(args \\ @arguments_ast, rest_variable) do
case length(args) do
0 ->
rest_variable
len ->
List.update_at(args, len - 1, fn last_arg ->
quote do
unquote(last_arg) | unquote(rest_variable)
end
end)
end
end
defmacro var(variable) do
Macro.escape(variable)
end
def inject_args(expr, args) do
map = Map.new(args)
Macro.postwalk(expr, fn
{arg_name, _, nil} = ast ->
case map do
%{^arg_name => arg_value} -> arg_value
_ -> ast
end
ast ->
ast
end)
end
defp validate_args_length(args) do
# raise on unexpected args
unquote(@branch_factor) = length(args)
args
end
def do_array(args) do
{:{}, [], args}
end
def sparse_map(args, fun) do
Enum.map(args, fn
nil -> nil
arg -> fun.(arg)
end)
end
# MAPPERS
def apply_mapper(fun) do
fn arg ->
quote do
unquote(fun).(unquote(arg))
end
end
end
# FIND
defmacro find_cond_tail(size,
do: [{:->, _, [[condition], returned]}, {:->, _, [_, default]}]
) do
clauses =
arguments()
|> Enum.with_index()
|> Enum.flat_map(fn {arg, i} ->
stop_check = quote do: (unquote(i) === unquote(size) -> unquote(default))
cond_check =
quote do
unquote(inject_args(condition, arg: arg)) ->
unquote(inject_args(returned, arg: arg, i: i))
end
if i > 0 do
stop_check ++ cond_check
else
cond_check
end
end)
final_clause = quote do: (true -> unquote(default))
quote do
cond do
unquote(clauses ++ final_clause)
end
end
end
defmacro find_cond_trie(do: [{:->, _, [[condition], returned]}, {:->, _, [_, default]}]) do
clauses =
arguments()
|> Enum.with_index()
|> Enum.flat_map(fn {arg, i} ->
stop_check = quote do: (unquote(arg) === nil -> unquote(default))
cond_check =
quote do
unquote(inject_args(condition, arg: arg)) ->
unquote(inject_args(returned, arg: arg, i: i))
end
if i > 0 do
stop_check ++ cond_check
else
cond_check
end
end)
final_clause = quote do: (true -> unquote(default))
quote do
cond do
unquote(clauses ++ final_clause)
end
end
end
defmacro find_cond_leaf(do: [{:->, _, [[condition], returned]}, {:->, _, [_, default]}]) do
clauses =
arguments()
|> Enum.with_index()
|> Enum.flat_map(fn {arg, i} ->
quote do
unquote(inject_args(condition, arg: arg)) ->
unquote(inject_args(returned, arg: arg, i: i))
end
end)
final_clause = quote do: (true -> unquote(default))
quote do
cond do
unquote(clauses ++ final_clause)
end
end
end
# LEFT FOLDS
defmacro def_foldl(header, do: body) do
{fun_name, [{arg_name, _, nil}, acc | rest_args]} = Macro.decompose_call(header)
expanded_body = Macro.expand(body, __CALLER__)
acc_params = parse_acc_params(acc)
do_def_foldl(fun_name, arg_name, acc_params, rest_args, expanded_body)
end
defp do_def_foldl(fun_name, arg_name, acc_params, rest_args, expanded_body) do
acc_var = elem(acc_params, 1)
tail_fun_name = fun_name_with_suffix(fun_name, :tail)
trie_fun_name = fun_name_with_suffix(fun_name, :trie)
[
do_def_foldl_trie(trie_fun_name, arg_name, acc_var, rest_args, expanded_body),
do_def_foldl_tail(tail_fun_name, arg_name, acc_var, rest_args, expanded_body),
case acc_params do
{:acc_var, acc_var} ->
do_foldl_with_acc(fun_name, tail_fun_name, trie_fun_name, acc_var, rest_args)
{:acc_value, _acc_var, acc_value} ->
do_foldl_with_value(fun_name, tail_fun_name, trie_fun_name, acc_value, rest_args)
{:acc_first, acc_var} ->
trie_left_fun_name = fun_name_with_suffix(fun_name, :trie_left)
[
do_def_foldl_trie_left(
trie_left_fun_name,
trie_fun_name,
arg_name,
acc_var,
rest_args,
expanded_body
),
do_foldl_with_first(fun_name, tail_fun_name, trie_left_fun_name, rest_args)
]
end
]
|> List.flatten()
end
defp do_foldl_with_acc(fun_name, tail_fun_name, trie_fun_name, acc_var, rest_args) do
quote do
def unquote(fun_name)(vector, unquote(acc_var), unquote_splicing(rest_args))
def unquote(fun_name)(
{size, 0, _, _, tail, _first},
unquote(acc_var),
unquote_splicing(rest_args)
) do
unquote(tail_fun_name)(
tail,
unquote(@branch_factor) - size,
unquote(acc_var),
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{size, tail_offset, level, trie, tail, _first},
unquote(acc_var),
unquote_splicing(rest_args)
) do
new_acc =
unquote(trie_fun_name)(trie, level, unquote(acc_var), unquote_splicing(rest_args))
unquote(tail_fun_name)(
tail,
unquote(@branch_factor) + tail_offset - size,
new_acc,
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{_},
unquote(acc_var),
unquote_splicing(for _ <- rest_args, do: @wildcard)
) do
unquote(acc_var)
end
end
end
defp do_foldl_with_value(
fun_name,
tail_fun_name,
trie_left_fun_name,
acc_value,
rest_args
) do
quote do
def unquote(fun_name)(vector, unquote_splicing(rest_args))
def unquote(fun_name)({size, 0, _, _, tail, _first}, unquote_splicing(rest_args)) do
unquote(tail_fun_name)(
tail,
unquote(@branch_factor) - size,
unquote(acc_value),
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{size, tail_offset, level, trie, tail, _first},
unquote_splicing(rest_args)
) do
new_acc =
unquote(trie_left_fun_name)(
trie,
level,
unquote(acc_value),
unquote_splicing(rest_args)
)
unquote(tail_fun_name)(
tail,
unquote(@branch_factor) + tail_offset - size,
new_acc,
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{_},
unquote_splicing(for _ <- rest_args, do: @wildcard)
) do
unquote(acc_value)
end
end
end
defp do_foldl_with_first(fun_name, tail_fun_name, trie_left_fun_name, rest_args) do
quote do
def unquote(fun_name)(vector, unquote_splicing(rest_args))
def unquote(fun_name)({size, 0, _, _, tail, _first}, unquote_splicing(rest_args)) do
acc = :erlang.element(unquote(@branch_factor + 1) - size, tail)
unquote(tail_fun_name)(
tail,
unquote(@branch_factor) + 1 - size,
acc,
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{size, tail_offset, level, trie, tail, _first},
unquote_splicing(rest_args)
) do
new_acc = unquote(trie_left_fun_name)(trie, level, unquote_splicing(rest_args))
unquote(tail_fun_name)(
tail,
unquote(@branch_factor) + tail_offset - size,
new_acc,
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{_},
unquote_splicing(for _ <- rest_args, do: @wildcard)
) do
raise Enum.EmptyError
end
end
end
defp do_def_foldl_tail(fun_name, arg_name, acc_var, rest_args, expanded_body) do
value_var = Macro.var(:value, nil)
quote do
defp unquote(fun_name)(tail, i, unquote(acc_var), unquote_splicing(rest_args))
defp unquote(fun_name)(
_tail,
_i = unquote(@branch_factor),
unquote(acc_var),
unquote_splicing(for _ <- rest_args, do: @wildcard)
) do
unquote(acc_var)
end
defp unquote(fun_name)(tail, i, unquote(acc_var), unquote_splicing(rest_args)) do
i = i + 1
unquote(value_var) = :erlang.element(i, tail)
new_acc =
unquote(
inject_reducer_variables(
expanded_body,
arg_name,
value_var,
acc_var,
acc_var
)
)
unquote(fun_name)(tail, i, new_acc, unquote_splicing(rest_args))
end
end
end
defp do_def_foldl_trie(fun_name, arg_name, acc_var, rest_args, expanded_body) do
quote do
defp unquote(fun_name)(trie, level, unquote(acc_var), unquote_splicing(rest_args))
defp unquote(fun_name)(
unquote(array()),
_level = 0,
unquote(acc_var),
unquote_splicing(rest_args)
) do
unquote(
arguments()
|> Enum.reduce(acc_var, fn arg_ast, acc_ast ->
inject_reducer_variables(expanded_body, arg_name, arg_ast, acc_var, acc_ast)
end)
)
end
defp unquote(fun_name)(
unquote(array()),
level,
unquote(acc_var),
unquote_splicing(rest_args)
) do
child_level = level - unquote(@bits)
unquote(
arguments()
|> Enum.reduce(acc_var, fn arg, acc ->
quote do
acc = unquote(acc)
case unquote(arg) do
nil -> acc
value -> unquote(fun_name)(value, child_level, acc, unquote_splicing(rest_args))
end
end
end)
)
end
end
end
defp do_def_foldl_trie_left(
left_fun_name,
rest_fun_name,
arg_name,
acc_var,
rest_args,
expanded_body
) do
[first_arg | right_args] = arguments()
left_acc =
quote do
unquote(left_fun_name)(unquote(first_arg), child_level, unquote_splicing(rest_args))
end
quote do
defp unquote(left_fun_name)(trie, level, unquote_splicing(rest_args))
defp unquote(left_fun_name)(
unquote(array()),
_level = 0,
unquote_splicing(rest_args)
) do
unquote(
arguments()
|> Enum.reduce(fn arg_ast, acc_ast ->
inject_reducer_variables(expanded_body, arg_name, arg_ast, acc_var, acc_ast)
end)
)
end
defp unquote(left_fun_name)(
unquote(array()),
level,
unquote_splicing(rest_args)
) do
child_level = level - unquote(@bits)
unquote(
Enum.reduce(right_args, left_acc, fn arg, acc ->
quote do
acc = unquote(acc)
case unquote(arg) do
nil ->
acc
value ->
unquote(rest_fun_name)(value, child_level, acc, unquote_splicing(rest_args))
end
end
end)
)
end
end
end
# RIGHT FOLDS
defmacro def_foldr(header, do: body) do
{fun_name, [{arg_name, _, nil}, acc | rest_args]} = Macro.decompose_call(header)
expanded_body = Macro.expand(body, __CALLER__)
acc_params = parse_acc_params(acc)
do_def_foldr(fun_name, arg_name, acc_params, rest_args, expanded_body)
end
defp do_def_foldr(fun_name, arg_name, acc_params, rest_args, expanded_body) do
acc_var = elem(acc_params, 1)
tail_fun_name = fun_name_with_suffix(fun_name, :tail)
trie_fun_name = fun_name_with_suffix(fun_name, :trie)
[
do_def_foldr_trie(trie_fun_name, arg_name, acc_var, rest_args, expanded_body),
do_def_foldr_tail(tail_fun_name, arg_name, acc_var, rest_args, expanded_body),
case acc_params do
{:acc_var, acc_var} ->
do_foldr_with_acc(fun_name, tail_fun_name, trie_fun_name, acc_var, rest_args)
{:acc_value, _acc_var, acc_value} ->
do_foldr_with_value(fun_name, tail_fun_name, trie_fun_name, acc_value, rest_args)
end
]
|> List.flatten()
end
defp do_foldr_with_acc(fun_name, tail_fun_name, trie_fun_name, acc_var, rest_args) do
quote do
def unquote(fun_name)(vector, unquote(acc_var), unquote_splicing(rest_args))
def unquote(fun_name)(
{size, 0, _, _, tail, _first},
unquote(acc_var),
unquote_splicing(rest_args)
) do
unquote(tail_fun_name)(
tail,
unquote(@branch_factor),
unquote(@branch_factor) - size,
unquote(acc_var),
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{size, tail_offset, level, trie, tail, _first},
unquote(acc_var),
unquote_splicing(rest_args)
) do
new_acc =
unquote(tail_fun_name)(
tail,
unquote(@branch_factor),
unquote(@branch_factor) + tail_offset - size,
unquote(acc_var),
unquote_splicing(rest_args)
)
unquote(trie_fun_name)(trie, level, new_acc, unquote_splicing(rest_args))
end
def unquote(fun_name)(
{_},
unquote(acc_var),
unquote_splicing(for _ <- rest_args, do: @wildcard)
) do
unquote(acc_var)
end
end
end
defp do_foldr_with_value(
fun_name,
tail_fun_name,
trie_left_fun_name,
acc_value,
rest_args
) do
quote do
def unquote(fun_name)(vector, unquote_splicing(rest_args))
def unquote(fun_name)({size, 0, _, _, tail, _first}, unquote_splicing(rest_args)) do
unquote(tail_fun_name)(
tail,
unquote(@branch_factor),
unquote(@branch_factor) - size,
unquote(acc_value),
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{size, tail_offset, level, trie, tail, _first},
unquote_splicing(rest_args)
) do
new_acc =
unquote(tail_fun_name)(
tail,
unquote(@branch_factor),
unquote(@branch_factor) + tail_offset - size,
unquote(acc_value),
unquote_splicing(rest_args)
)
unquote(trie_left_fun_name)(
trie,
level,
new_acc,
unquote_splicing(rest_args)
)
end
def unquote(fun_name)(
{_},
unquote_splicing(for _ <- rest_args, do: @wildcard)
) do
unquote(acc_value)
end
end
end
defp do_def_foldr_tail(fun_name, arg_name, acc_var, rest_args, expanded_body) do
value_var = Macro.var(:value, nil)
quote do
defp unquote(fun_name)(tail, i, until, unquote(acc_var), unquote_splicing(rest_args))
defp unquote(fun_name)(
_tail,
_i = until,
until,
unquote(acc_var),
unquote_splicing(for _ <- rest_args, do: @wildcard)
) do
unquote(acc_var)
end
defp unquote(fun_name)(tail, i, until, unquote(acc_var), unquote_splicing(rest_args)) do
unquote(value_var) = :erlang.element(i, tail)
new_acc =
unquote(
inject_reducer_variables(
expanded_body,
arg_name,
value_var,
acc_var,
acc_var
)
)
unquote(fun_name)(tail, i - 1, until, new_acc, unquote_splicing(rest_args))
end
end
end
defp do_def_foldr_trie(fun_name, arg_name, acc_var, rest_args, expanded_body) do
quote do
defp unquote(fun_name)(trie, level, unquote(acc_var), unquote_splicing(rest_args))
defp unquote(fun_name)(
unquote(array()),
_level = 0,
unquote(acc_var),
unquote_splicing(rest_args)
) do
unquote(
reversed_arguments()
|> Enum.reduce(acc_var, fn arg_ast, acc_ast ->
inject_reducer_variables(expanded_body, arg_name, arg_ast, acc_var, acc_ast)
end)
)
end
defp unquote(fun_name)(
unquote(array()),
level,
unquote(acc_var),
unquote_splicing(rest_args)
) do
child_level = level - unquote(@bits)
unquote(
reversed_arguments()
|> Enum.reduce(acc_var, fn arg, acc ->
quote do
acc = unquote(acc)
case unquote(arg) do
nil -> acc
value -> unquote(fun_name)(value, child_level, acc, unquote_splicing(rest_args))
end
end
end)
)
end
end
end
defp parse_acc_params({acc_name, _, nil}) do
{:acc_var, Macro.var(acc_name, nil)}
end
defp parse_acc_params({:\\, _, [{acc_name, _, nil}, acc_value]}) do
case acc_value do
{:first, _, []} ->
{:acc_first, Macro.var(acc_name, nil)}
_ ->
{:acc_value, Macro.var(acc_name, nil), acc_value}
end
end
defp inject_reducer_variables(expr, arg_name, arg_ast, acc_var, acc_ast) do
quote do
unquote(acc_var) = unquote(acc_ast)
unquote(inject_args(expr, %{arg_name => arg_ast}))
end
end
defp fun_name_with_suffix(fun_name, suffix) when is_atom(fun_name) and is_atom(suffix) do
String.to_atom("#{fun_name}_#{suffix}")
end
end