685 lines
27 KiB
Erlang
685 lines
27 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% @copyright (C) 2018, Aeternity Anstalt
|
|
%%% @doc
|
|
%%% Compiler builtin functions for Aeterinty Sophia language.
|
|
%%% @end
|
|
%%% Created : 20 Dec 2018
|
|
%%%
|
|
%%%-------------------------------------------------------------------
|
|
|
|
-module(aeso_builtins).
|
|
|
|
-export([ builtin_function/1
|
|
, bytes_to_raw_string/2
|
|
, check_event_type/1
|
|
, used_builtins/1 ]).
|
|
|
|
-import(aeso_ast_to_icode, [prim_call/5]).
|
|
|
|
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
|
-include("aeso_icode.hrl").
|
|
|
|
used_builtins(#funcall{ function = #var_ref{ name = {builtin, Builtin} }, args = Args }) ->
|
|
lists:umerge(dep_closure([Builtin]), used_builtins(Args));
|
|
used_builtins([H|T]) ->
|
|
lists:umerge(used_builtins(H), used_builtins(T));
|
|
used_builtins(T) when is_tuple(T) ->
|
|
used_builtins(tuple_to_list(T));
|
|
used_builtins(M) when is_map(M) ->
|
|
used_builtins(maps:to_list(M));
|
|
used_builtins(_) -> [].
|
|
|
|
builtin_deps(Builtin) ->
|
|
lists:usort(builtin_deps1(Builtin)).
|
|
|
|
builtin_deps1({map_lookup_default, Type}) -> [{map_lookup, Type}];
|
|
builtin_deps1({map_get, Type}) -> [{map_lookup, Type}];
|
|
builtin_deps1(map_member) -> [{map_lookup, word}];
|
|
builtin_deps1({map_upd, Type}) -> [{map_get, Type}, map_put];
|
|
builtin_deps1({map_upd_default, Type}) -> [{map_lookup_default, Type}, map_put];
|
|
builtin_deps1(map_from_list) -> [map_put];
|
|
builtin_deps1(str_equal) -> [str_equal_p];
|
|
builtin_deps1(string_concat) -> [string_concat_inner1, string_copy, string_shift_copy];
|
|
builtin_deps1(int_to_str) -> [{baseX_int, 10}];
|
|
builtin_deps1(addr_to_str) -> [{baseX_int, 58}];
|
|
builtin_deps1({baseX_int, X}) -> [{baseX_int_pad, X}];
|
|
builtin_deps1({baseX_int_pad, X}) -> [{baseX_int_encode, X}];
|
|
builtin_deps1({baseX_int_encode, X}) -> [{baseX_int_encode_, X}, {baseX_tab, X}, {baseX_digits, X}];
|
|
builtin_deps1({bytes_to_str, _}) -> [bytes_to_str_worker, bytes_to_str_worker_x];
|
|
builtin_deps1(string_reverse) -> [string_reverse_];
|
|
builtin_deps1(require) -> [abort];
|
|
builtin_deps1(_) -> [].
|
|
|
|
dep_closure(Deps) ->
|
|
case lists:umerge(lists:map(fun builtin_deps/1, Deps)) of
|
|
[] -> Deps;
|
|
Deps1 -> lists:umerge(Deps, dep_closure(Deps1))
|
|
end.
|
|
|
|
%% Helper functions/macros
|
|
v(X) when is_atom(X) -> v(atom_to_list(X));
|
|
v(X) when is_list(X) -> #var_ref{name = X}.
|
|
|
|
option_none() -> {tuple, [{integer, 0}]}.
|
|
option_some(X) -> {tuple, [{integer, 1}, X]}.
|
|
|
|
-define(HASH_BYTES, 32).
|
|
|
|
-define(call(Fun, Args), #funcall{ function = #var_ref{ name = {builtin, Fun} }, args = Args }).
|
|
-define(I(X), {integer, X}).
|
|
-define(V(X), v(X)).
|
|
-define(A(Op), aeb_opcodes:mnemonic(Op)).
|
|
-define(LET(Var, Expr, Body), {switch, Expr, [{v(Var), Body}]}).
|
|
-define(DEREF(Var, Ptr, Body), {switch, operand(Ptr), [{{tuple, [v(Var)]}, Body}]}).
|
|
-define(NXT(Ptr), op('+', Ptr, 32)).
|
|
-define(NEG(A), op('/', A, {unop, '-', {integer, 1}})).
|
|
-define(BYTE(Ix, Word), op('byte', Ix, Word)).
|
|
|
|
-define(EQ(A, B), op('==', A, B)).
|
|
-define(LT(A, B), op('<', A, B)).
|
|
-define(GT(A, B), op('>', A, B)).
|
|
-define(ADD(A, B), op('+', A, B)).
|
|
-define(SUB(A, B), op('-', A, B)).
|
|
-define(MUL(A, B), op('*', A, B)).
|
|
-define(DIV(A, B), op('div', A, B)).
|
|
-define(MOD(A, B), op('mod', A, B)).
|
|
-define(EXP(A, B), op('^', A, B)).
|
|
-define(AND(A, B), op('&&', A, B)).
|
|
|
|
%% Bit shift operations takes their arguments backwards!?
|
|
-define(BSL(X, B), op('bsl', ?MUL(B, 8), X)).
|
|
-define(BSR(X, B), op('bsr', ?MUL(B, 8), X)).
|
|
|
|
op(Op, A, B) -> simpl({binop, Op, operand(A), operand(B)}).
|
|
|
|
%% We generate a lot of B * 8 for integer B from BSL and BSR.
|
|
simpl({binop, '*', {integer, A}, {integer, B}}) when A >= 0, B >= 0, A * B < 1 bsl 256 ->
|
|
{integer, A * B};
|
|
simpl(Op) -> Op.
|
|
|
|
|
|
operand(A) when is_atom(A) -> v(A);
|
|
operand(I) when is_integer(I) -> {integer, I};
|
|
operand(T) -> T.
|
|
|
|
check_event_type(Icode) ->
|
|
case maps:get(event_type, Icode) of
|
|
{variant_t, Cons} ->
|
|
check_event_type(Cons, Icode);
|
|
_ ->
|
|
error({event_should_be_variant_type})
|
|
end.
|
|
|
|
check_event_type(Evts, Icode) ->
|
|
[ check_event_type(Name, Ix, T, Icode)
|
|
|| {constr_t, Ann, {con, _, Name}, Types} <- Evts,
|
|
{Ix, T} <- lists:zip(aeso_syntax:get_ann(indices, Ann), Types) ].
|
|
|
|
check_event_type(EvtName, Ix, Type, Icode) ->
|
|
VMType =
|
|
try
|
|
aeso_ast_to_icode:ast_typerep(Type, Icode)
|
|
catch _:_ ->
|
|
error({EvtName, could_not_resolve_type, Type})
|
|
end,
|
|
case {Ix, VMType, Type} of
|
|
{indexed, word, _} -> ok;
|
|
{notindexed, string, _} -> ok;
|
|
{notindexed, _, {bytes_t, _, N}} when N > 32 -> ok;
|
|
{indexed, _, _} -> error({EvtName, indexed_field_should_be_word, is, VMType});
|
|
{notindexed, _, _} -> error({EvtName, payload_should_be_string, is, VMType})
|
|
end.
|
|
|
|
bfun(B, {IArgs, IExpr, IRet}) ->
|
|
{{builtin, B}, [private], IArgs, IExpr, IRet}.
|
|
|
|
builtin_function(BF) ->
|
|
case BF of
|
|
{event, EventT} -> bfun(BF, builtin_event(EventT));
|
|
abort -> bfun(BF, builtin_abort());
|
|
block_hash -> bfun(BF, builtin_block_hash());
|
|
require -> bfun(BF, builtin_require());
|
|
{map_lookup, Type} -> bfun(BF, builtin_map_lookup(Type));
|
|
map_put -> bfun(BF, builtin_map_put());
|
|
map_delete -> bfun(BF, builtin_map_delete());
|
|
map_size -> bfun(BF, builtin_map_size());
|
|
{map_get, Type} -> bfun(BF, builtin_map_get(Type));
|
|
{map_lookup_default, Type} -> bfun(BF, builtin_map_lookup_default(Type));
|
|
map_member -> bfun(BF, builtin_map_member());
|
|
{map_upd, Type} -> bfun(BF, builtin_map_upd(Type));
|
|
{map_upd_default, Type} -> bfun(BF, builtin_map_upd_default(Type));
|
|
map_from_list -> bfun(BF, builtin_map_from_list());
|
|
list_concat -> bfun(BF, builtin_list_concat());
|
|
string_length -> bfun(BF, builtin_string_length());
|
|
string_concat -> bfun(BF, builtin_string_concat());
|
|
string_concat_inner1 -> bfun(BF, builtin_string_concat_inner1());
|
|
string_copy -> bfun(BF, builtin_string_copy());
|
|
string_shift_copy -> bfun(BF, builtin_string_shift_copy());
|
|
str_equal_p -> bfun(BF, builtin_str_equal_p());
|
|
str_equal -> bfun(BF, builtin_str_equal());
|
|
popcount -> bfun(BF, builtin_popcount());
|
|
int_to_str -> bfun(BF, builtin_int_to_str());
|
|
addr_to_str -> bfun(BF, builtin_addr_to_str());
|
|
{baseX_int, X} -> bfun(BF, builtin_baseX_int(X));
|
|
{baseX_digits, X} -> bfun(BF, builtin_baseX_digits(X));
|
|
{baseX_tab, X} -> bfun(BF, builtin_baseX_tab(X));
|
|
{baseX_int_pad, X} -> bfun(BF, builtin_baseX_int_pad(X));
|
|
{baseX_int_encode, X} -> bfun(BF, builtin_baseX_int_encode(X));
|
|
{baseX_int_encode_, X} -> bfun(BF, builtin_baseX_int_encode_(X));
|
|
{bytes_to_int, N} -> bfun(BF, builtin_bytes_to_int(N));
|
|
{bytes_to_str, N} -> bfun(BF, builtin_bytes_to_str(N));
|
|
{bytes_concat, A, B} -> bfun(BF, builtin_bytes_concat(A, B));
|
|
{bytes_split, A, B} -> bfun(BF, builtin_bytes_split(A, B));
|
|
bytes_to_str_worker -> bfun(BF, builtin_bytes_to_str_worker());
|
|
bytes_to_str_worker_x -> bfun(BF, builtin_bytes_to_str_worker_x());
|
|
string_reverse -> bfun(BF, builtin_string_reverse());
|
|
string_reverse_ -> bfun(BF, builtin_string_reverse_())
|
|
end.
|
|
|
|
%% Event primitive (dependent on Event type)
|
|
%%
|
|
%% We need to switch on the event and prepare the correct #event for icode_to_asm
|
|
%% NOTE: we assume all errors are already checked!
|
|
builtin_event(EventT) ->
|
|
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
|
VIx = fun(Ix) -> v(lists:concat(["v", Ix])) end,
|
|
ArgPats = fun(Ts) -> [ VIx(Ix) || Ix <- lists:seq(0, length(Ts) - 1) ] end,
|
|
Payload = %% Should put data ptr, length on stack.
|
|
fun([]) -> {inline_asm, [A(?PUSH1), 0, A(?PUSH1), 0]};
|
|
([{{id, _, "string"}, V}]) ->
|
|
{seq, [V, {inline_asm, [A(?DUP1), A(?MLOAD), %% length, ptr
|
|
A(?SWAP1), A(?PUSH1), 32, A(?ADD)]}]}; %% ptr+32, length
|
|
([{{bytes_t, _, N}, V}]) -> {seq, [V, {integer, N}, {inline_asm, A(?SWAP1)}]}
|
|
end,
|
|
Ix =
|
|
fun({bytes_t, _, N}, V) when N < 32 -> ?BSR(V, 32 - N);
|
|
(_, V) -> V end,
|
|
Clause =
|
|
fun(_Tag, {con, _, Con}, IxTypes) ->
|
|
Types = [ T || {_Ix, T} <- IxTypes ],
|
|
Indexed = [ Ix(Type, Var) || {Var, {indexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
|
Data = [ {Type, Var} || {Var, {notindexed, Type}} <- lists:zip(ArgPats(Types), IxTypes) ],
|
|
{ok, <<EvtIndexN:256>>} = eblake2:blake2b(?HASH_BYTES, list_to_binary(Con)),
|
|
EvtIndex = {integer, EvtIndexN},
|
|
{event, lists:reverse(Indexed) ++ [EvtIndex], Payload(Data)}
|
|
end,
|
|
Pat = fun(Tag, Types) -> {tuple, [{integer, Tag} | ArgPats(Types)]} end,
|
|
|
|
{variant_t, Cons} = EventT,
|
|
Tags = lists:seq(0, length(Cons) - 1),
|
|
|
|
{[{"e", event}],
|
|
{switch, v(e),
|
|
[{Pat(Tag, Types), Clause(Tag, Con, lists:zip(aeso_syntax:get_ann(indices, Ann), Types))}
|
|
|| {Tag, {constr_t, Ann, Con, Types}} <- lists:zip(Tags, Cons) ]},
|
|
{tuple, []}}.
|
|
|
|
%% Abort primitive.
|
|
builtin_abort() ->
|
|
A = fun(X) -> aeb_opcodes:mnemonic(X) end,
|
|
{[{"s", string}],
|
|
{inline_asm, [A(?PUSH1),0, %% Push a dummy 0 for the first arg
|
|
A(?REVERT)]}, %% Stack: 0,Ptr
|
|
{tuple,[]}}.
|
|
|
|
builtin_block_hash() ->
|
|
{[{"height", word}],
|
|
?LET(hash, #prim_block_hash{ height = ?V(height)},
|
|
{ifte, ?EQ(hash, 0), option_none(), option_some(?V(hash))}),
|
|
aeso_icode:option_typerep(word)}.
|
|
|
|
builtin_require() ->
|
|
{[{"c", word}, {"msg", string}],
|
|
{ifte, ?V(c), {tuple, []}, ?call(abort, [?V(msg)])},
|
|
{tuple, []}}.
|
|
|
|
%% Map primitives
|
|
builtin_map_lookup(Type) ->
|
|
Ret = aeso_icode:option_typerep(Type),
|
|
{[{"m", word}, {"k", word}],
|
|
prim_call(?PRIM_CALL_MAP_GET, #integer{value = 0},
|
|
[#var_ref{name = "m"}, #var_ref{name = "k"}],
|
|
[word, word], Ret),
|
|
Ret}.
|
|
|
|
builtin_map_put() ->
|
|
%% We don't need the types for put.
|
|
{[{"m", word}, {"k", word}, {"v", word}],
|
|
prim_call(?PRIM_CALL_MAP_PUT, #integer{value = 0},
|
|
[v(m), v(k), v(v)], [word, word, word], word),
|
|
word}.
|
|
|
|
builtin_map_delete() ->
|
|
{[{"m", word}, {"k", word}],
|
|
prim_call(?PRIM_CALL_MAP_DELETE, #integer{value = 0},
|
|
[v(m), v(k)], [word, word], word),
|
|
word}.
|
|
|
|
builtin_map_size() ->
|
|
{[{"m", word}],
|
|
prim_call(?PRIM_CALL_MAP_SIZE, #integer{value = 0},
|
|
[v(m)], [word], word),
|
|
word}.
|
|
|
|
%% Map builtins
|
|
builtin_map_get(Type) ->
|
|
%% function map_get(m, k) =
|
|
%% switch(map_lookup(m, k))
|
|
%% Some(v) => v
|
|
{[{"m", word}, {"k", word}],
|
|
{switch, ?call({map_lookup, Type}, [v(m), v(k)]), [{option_some(v(v)), v(v)}]},
|
|
Type}.
|
|
|
|
builtin_map_lookup_default(Type) ->
|
|
%% function map_lookup_default(m, k, default) =
|
|
%% switch(map_lookup(m, k))
|
|
%% None => default
|
|
%% Some(v) => v
|
|
{[{"m", word}, {"k", word}, {"default", Type}],
|
|
{switch, ?call({map_lookup, Type}, [v(m), v(k)]),
|
|
[{option_none(), v(default)},
|
|
{option_some(v(v)), v(v)}]},
|
|
Type}.
|
|
|
|
builtin_map_member() ->
|
|
%% function map_member(m, k) : bool =
|
|
%% switch(Map.lookup(m, k))
|
|
%% None => false
|
|
%% _ => true
|
|
{[{"m", word}, {"k", word}],
|
|
{switch, ?call({map_lookup, word}, [v(m), v(k)]),
|
|
[{option_none(), {integer, 0}},
|
|
{{var_ref, "_"}, {integer, 1}}]},
|
|
word}.
|
|
|
|
builtin_map_upd(Type) ->
|
|
%% function map_upd(map, key, fun) =
|
|
%% map_put(map, key, fun(map_get(map, key)))
|
|
{[{"map", word}, {"key", word}, {"valfun", word}],
|
|
?call(map_put,
|
|
[v(map), v(key),
|
|
#funcall{ function = v(valfun),
|
|
args = [?call({map_get, Type}, [v(map), v(key)])] }]),
|
|
word}.
|
|
|
|
builtin_map_upd_default(Type) ->
|
|
%% function map_upd(map, key, val, fun) =
|
|
%% map_put(map, key, fun(map_lookup_default(map, key, val)))
|
|
{[{"map", word}, {"key", word}, {"val", word}, {"valfun", word}],
|
|
?call(map_put,
|
|
[v(map), v(key),
|
|
#funcall{ function = v(valfun),
|
|
args = [?call({map_lookup_default, Type}, [v(map), v(key), v(val)])] }]),
|
|
word}.
|
|
|
|
builtin_map_from_list() ->
|
|
%% function map_from_list(xs, acc) =
|
|
%% switch(xs)
|
|
%% [] => acc
|
|
%% (k, v) :: xs => map_from_list(xs, acc { [k] = v })
|
|
{[{"xs", {list, {tuple, [word, word]}}}, {"acc", word}],
|
|
{switch, v(xs),
|
|
[{{list, []}, v(acc)},
|
|
{{binop, '::', {tuple, [v(k), v(v)]}, v(ys)},
|
|
?call(map_from_list,
|
|
[v(ys), ?call(map_put, [v(acc), v(k), v(v)])])}]},
|
|
word}.
|
|
|
|
%% list_concat
|
|
%%
|
|
%% Concatenates two lists.
|
|
builtin_list_concat() ->
|
|
{[{"l1", {list, word}}, {"l2", {list, word}}],
|
|
{switch, v(l1),
|
|
[{{list, []}, v(l2)},
|
|
{{binop, '::', v(hd), v(tl)},
|
|
{binop, '::', v(hd), ?call(list_concat, [v(tl), v(l2)])}}
|
|
]
|
|
},
|
|
word}.
|
|
|
|
builtin_string_length() ->
|
|
%% function length(str) =
|
|
%% switch(str)
|
|
%% {n} -> n // (ab)use the representation
|
|
{[{"s", string}],
|
|
?DEREF(n, s, ?V(n)),
|
|
word}.
|
|
|
|
%% str_concat - concatenate two strings
|
|
%%
|
|
%% Unless the second string is the empty string, a new string is created at the
|
|
%% top of the Heap and the address to it is returned. The tricky bit is when
|
|
%% the words from the second string has to be shifted to fit next to the first
|
|
%% string.
|
|
builtin_string_concat() ->
|
|
{[{"s1", string}, {"s2", string}],
|
|
?DEREF(n1, s1,
|
|
?DEREF(n2, s2,
|
|
{ifte, ?EQ(n1, 0),
|
|
?V(s2), %% First string is empty return second string
|
|
{ifte, ?EQ(n2, 0),
|
|
?V(s1), %% Second string is empty return first string
|
|
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
|
{seq, [?ADD(n1, n2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}, %% Store total len
|
|
?call(string_concat_inner1, [?V(n1), ?NXT(s1), ?V(n2), ?NXT(s2)]),
|
|
{inline_asm, [?A(?POP)]}, %% Discard fun ret val
|
|
?V(ret) %% Put the actual return value
|
|
]})}
|
|
}
|
|
)),
|
|
word}.
|
|
|
|
builtin_string_concat_inner1() ->
|
|
%% Copy all whole words from the first string, and set up for word fusion
|
|
%% Special case when the length of the first string is divisible by 32.
|
|
{[{"n1", word}, {"p1", pointer}, {"n2", word}, {"p2", pointer}],
|
|
?LET(w1, ?call(string_copy, [?V(n1), ?V(p1)]),
|
|
?LET(nx, ?MOD(n1, 32),
|
|
{ifte, ?EQ(nx, 0),
|
|
?LET(w2, ?call(string_copy, [?V(n2), ?V(p2)]),
|
|
{seq, [?V(w2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]}),
|
|
?call(string_shift_copy, [?V(nx), ?V(w1), ?V(n2), ?V(p2)])
|
|
})),
|
|
word}.
|
|
|
|
builtin_string_copy() ->
|
|
{[{"n", word}, {"p", pointer}],
|
|
?DEREF(w, p,
|
|
{ifte, ?GT(n, 31),
|
|
{seq, [?V(w), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
|
?call(string_copy, [?SUB(n, 32), ?NXT(p)])]},
|
|
?V(w)
|
|
}),
|
|
word}.
|
|
|
|
builtin_string_shift_copy() ->
|
|
{[{"off", word}, {"dst", word}, {"n", word}, {"p", pointer}],
|
|
?DEREF(w, p,
|
|
{seq, [?ADD(dst, ?BSR(w, off)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
|
{ifte, ?GT(n, ?SUB(32, off)),
|
|
?call(string_shift_copy, [?V(off), ?BSL(w, ?SUB(32, off)), ?SUB(n, 32), ?NXT(p)]),
|
|
{inline_asm, [?A(?MSIZE)]}}]
|
|
}),
|
|
word}.
|
|
|
|
builtin_str_equal_p() ->
|
|
%% function str_equal_p(n, p1, p2) =
|
|
%% if(n =< 0) true
|
|
%% else
|
|
%% let w1 = *p1
|
|
%% let w2 = *p2
|
|
%% w1 == w2 && str_equal_p(n - 32, p1 + 32, p2 + 32)
|
|
{[{"n", word}, {"p1", pointer}, {"p2", pointer}],
|
|
{ifte, ?LT(n, 1),
|
|
?I(1),
|
|
?DEREF(w1, p1,
|
|
?DEREF(w2, p2,
|
|
?AND(?EQ(w1, w2),
|
|
?call(str_equal_p, [?SUB(n, 32), ?NXT(p1), ?NXT(p2)]))))},
|
|
word}.
|
|
|
|
builtin_str_equal() ->
|
|
%% function str_equal(s1, s2) =
|
|
%% let n1 = length(s1)
|
|
%% let n2 = length(s2)
|
|
%% n1 == n2 && str_equal_p(n1, s1 + 32, s2 + 32)
|
|
{[{"s1", string}, {"s2", string}],
|
|
?DEREF(n1, s1,
|
|
?DEREF(n2, s2,
|
|
?AND(?EQ(n1, n2), ?call(str_equal_p, [?V(n1), ?NXT(s1), ?NXT(s2)]))
|
|
)),
|
|
word}.
|
|
|
|
%% Count the number of 1s in a bit field.
|
|
builtin_popcount() ->
|
|
%% function popcount(bits, acc) =
|
|
%% if (bits == 0) acc
|
|
%% else popcount(bits bsr 1, acc + bits band 1)
|
|
{[{"bits", word}, {"acc", word}],
|
|
{ifte, ?EQ(bits, 0),
|
|
?V(acc),
|
|
?call(popcount, [op('bsr', 1, bits), ?ADD(acc, op('band', bits, 1))])
|
|
}, word}.
|
|
|
|
builtin_int_to_str() ->
|
|
{[{"i", word}], ?call({baseX_int, 10}, [?V(i)]), word}.
|
|
|
|
builtin_baseX_tab(_X = 10) ->
|
|
{[{"ix", word}], ?ADD($0, ix), word};
|
|
builtin_baseX_tab(_X = 58) ->
|
|
<<Fst32:256>> = <<"123456789ABCDEFGHJKLMNPQRSTUVWXY">>,
|
|
<<Lst26:256>> = <<"Zabcdefghijkmnopqrstuvwxyz", 0:48>>,
|
|
{[{"ix", word}],
|
|
{ifte, ?LT(ix, 32),
|
|
?BYTE(ix, Fst32),
|
|
?BYTE(?SUB(ix, 32), Lst26)
|
|
},
|
|
word}.
|
|
|
|
builtin_baseX_int(X) ->
|
|
{[{"w", word}],
|
|
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
|
{seq, [?call({baseX_int_pad, X}, [?V(w), ?I(0), ?I(0)]), {inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
|
word}.
|
|
|
|
builtin_baseX_int_pad(X = 10) ->
|
|
{[{"src", word}, {"ix", word}, {"dst", word}],
|
|
{ifte, ?LT(src, 0),
|
|
?call({baseX_int_encode, X}, [?NEG(src), ?I(1), ?BSL($-, 31)]),
|
|
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)])},
|
|
word};
|
|
builtin_baseX_int_pad(X = 16) ->
|
|
{[{"src", word}, {"ix", word}, {"dst", word}],
|
|
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
|
|
word};
|
|
builtin_baseX_int_pad(X = 58) ->
|
|
{[{"src", word}, {"ix", word}, {"dst", word}],
|
|
{ifte, ?GT(?ADD(?DIV(ix, 31), ?BYTE(ix, src)), 0),
|
|
?call({baseX_int_encode, X}, [?V(src), ?V(ix), ?V(dst)]),
|
|
?call({baseX_int_pad, X}, [?V(src), ?ADD(ix, 1), ?ADD(dst, ?BSL($1, ?SUB(31, ix)))])},
|
|
word}.
|
|
|
|
builtin_baseX_int_encode(X) ->
|
|
{[{"src", word}, {"ix", word}, {"dst", word}],
|
|
?LET(n, ?call({baseX_digits, X}, [?V(src), ?I(0)]),
|
|
{seq, [?ADD(n, ?ADD(ix, 1)), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
|
?call({baseX_int_encode_, X}, [?V(src), ?V(dst), ?EXP(X, n), ?V(ix)])]}),
|
|
word}.
|
|
|
|
builtin_baseX_int_encode_(X) ->
|
|
{[{"src", word}, {"dst", word}, {"fac", word}, {"ix", word}],
|
|
{ifte, ?EQ(fac, 0),
|
|
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
|
{ifte, ?EQ(ix, 32),
|
|
%% We've filled a word, write it and start on new word
|
|
{seq, [?V(dst), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
|
?call({baseX_int_encode_, X}, [?V(src), ?I(0), ?V(fac), ?I(0)])]},
|
|
?call({baseX_int_encode_, X},
|
|
[?MOD(src, fac), ?ADD(dst, ?BSL(?call({baseX_tab, X}, [?DIV(src, fac)]), ?SUB(31, ix))),
|
|
?DIV(fac, X), ?ADD(ix, 1)])}
|
|
},
|
|
word}.
|
|
|
|
builtin_baseX_digits(X) ->
|
|
{[{"x0", word}, {"dgts", word}],
|
|
?LET(x1, ?DIV(x0, X),
|
|
{ifte, ?EQ(x1, 0), ?V(dgts), ?call({baseX_digits, X}, [?V(x1), ?ADD(dgts, 1)])}),
|
|
word}.
|
|
|
|
builtin_bytes_to_int(32) ->
|
|
{[{"w", word}], ?V(w), word};
|
|
builtin_bytes_to_int(N) when N < 32 ->
|
|
{[{"w", word}], ?BSR(w, 32 - N), word};
|
|
builtin_bytes_to_int(N) when N > 32 ->
|
|
LastFullWord = N div 32 - 1,
|
|
Body = case N rem 32 of
|
|
0 -> ?DEREF(n, ?ADD(b, LastFullWord * 32), ?V(n));
|
|
R ->
|
|
?DEREF(hi, ?ADD(b, LastFullWord * 32),
|
|
?DEREF(lo, ?ADD(b, (LastFullWord + 1) * 32),
|
|
?ADD(?BSR(lo, 32 - R), ?BSL(hi, R))))
|
|
end,
|
|
{[{"b", pointer}], Body, word}.
|
|
|
|
%% Two versions of this helper function, worker for sections not even 16 bytes long
|
|
%% and worker_x for the full sized chunks.
|
|
builtin_bytes_to_str_worker_x() ->
|
|
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
|
|
{[{"w", word}, {"offs", word}, {"acc", word}],
|
|
{ifte, ?EQ(offs, 16), {seq, [?V(acc), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
|
?LET(b, ?BYTE(offs, w),
|
|
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
|
|
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
|
|
?call(bytes_to_str_worker_x, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo))]))))
|
|
},
|
|
word}.
|
|
|
|
builtin_bytes_to_str_worker() ->
|
|
<<Tab:256>> = <<"0123456789ABCDEF________________">>,
|
|
{[{"w", word}, {"offs", word}, {"acc", word}, {"stop", word}],
|
|
{ifte, ?EQ(stop, offs), {seq, [?BSL(acc, ?MUL(2, ?SUB(16, offs))), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
|
?LET(b, ?BYTE(offs, w),
|
|
?LET(lo, ?BYTE(?MOD(b, 16), Tab),
|
|
?LET(hi, ?BYTE(op('bsr', 4 , b), Tab),
|
|
?call(bytes_to_str_worker, [?V(w), ?ADD(offs, 1), ?ADD(?BSL(acc, 2), ?ADD(?BSL(hi, 1), lo)), ?V(stop)]))))
|
|
},
|
|
word}.
|
|
|
|
builtin_bytes_to_str_body(Var, N) when N < 16 ->
|
|
[?call(bytes_to_str_worker, [?V(Var), ?I(0), ?I(0), ?I(N)])];
|
|
builtin_bytes_to_str_body(Var, 16) ->
|
|
[?call(bytes_to_str_worker_x, [?V(Var), ?I(0), ?I(0)])];
|
|
builtin_bytes_to_str_body(Var, N) when N < 32 ->
|
|
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
|
|
[?call(bytes_to_str_worker, [?BSL(Var, 16), ?I(0), ?I(0), ?I(N - 16)])];
|
|
builtin_bytes_to_str_body(Var, 32) ->
|
|
builtin_bytes_to_str_body(Var, 16) ++ [{inline_asm, [?A(?POP)]}] ++
|
|
[?call(bytes_to_str_worker_x, [?BSL(Var, 16), ?I(0), ?I(0)])];
|
|
builtin_bytes_to_str_body(Var, N) when N > 32 ->
|
|
WholeWords = ((N + 31) div 32) - 1,
|
|
lists:append(
|
|
[ [?DEREF(w, ?ADD(Var, 32 * I), {seq, builtin_bytes_to_str_body(w, 32)}), {inline_asm, [?A(?POP)]}]
|
|
|| I <- lists:seq(0, WholeWords - 1) ]) ++
|
|
[ ?DEREF(w, ?ADD(Var, 32 * WholeWords), {seq, builtin_bytes_to_str_body(w, N - WholeWords * 32)}) ].
|
|
|
|
builtin_bytes_to_str(N) when N =< 32 ->
|
|
{[{"w", word}],
|
|
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
|
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
|
|
builtin_bytes_to_str_body(w, N) ++
|
|
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
|
string};
|
|
builtin_bytes_to_str(N) when N > 32 ->
|
|
{[{"p", pointer}],
|
|
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
|
{seq, [?I(N * 2), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]}] ++
|
|
builtin_bytes_to_str_body(p, N) ++
|
|
[{inline_asm, [?A(?POP)]}, ?V(ret)]}),
|
|
string}.
|
|
|
|
builtin_string_reverse() ->
|
|
{[{"s", string}],
|
|
?DEREF(n, s,
|
|
?LET(ret, {inline_asm, [?A(?MSIZE)]},
|
|
{seq, [?V(n), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
|
?call(string_reverse_, [?NXT(s), ?I(0), ?I(31), ?SUB(?V(n), 1)]),
|
|
{inline_asm, [?A(?POP)]}, ?V(ret)]})),
|
|
word}.
|
|
|
|
builtin_string_reverse_() ->
|
|
{[{"p", pointer}, {"x", word}, {"i1", word}, {"i2", word}],
|
|
{ifte, ?LT(i2, 0),
|
|
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE), ?A(?MSIZE)]}]},
|
|
?LET(p1, ?ADD(p, ?MUL(?DIV(i2, 32), 32)),
|
|
?DEREF(w, p1,
|
|
?LET(b, ?BYTE(?MOD(i2, 32), w),
|
|
{ifte, ?LT(i1, 0),
|
|
{seq, [?V(x), {inline_asm, [?A(?MSIZE), ?A(?MSTORE)]},
|
|
?call(string_reverse_,
|
|
[?V(p), ?BSL(b, 31), ?I(30), ?SUB(i2, 1)])]},
|
|
?call(string_reverse_,
|
|
[?V(p), ?ADD(x, ?BSL(b, i1)), ?SUB(i1, 1), ?SUB(i2, 1)])})))},
|
|
word}.
|
|
|
|
builtin_addr_to_str() ->
|
|
{[{"a", word}], ?call({baseX_int, 58}, [?V(a)]), word}.
|
|
|
|
%% At most one word
|
|
%% | ..... | ========= | ........ |
|
|
%% Offs ^ ^- Len -^ TotalLen ^
|
|
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen =< 32 ->
|
|
%% Bytes are packed into a single word
|
|
Masked =
|
|
case Offs of
|
|
0 -> Bytes;
|
|
_ -> ?MOD(Bytes, 1 bsl ((32 - Offs) * 8))
|
|
end,
|
|
Unpadded =
|
|
case 32 - (Offs + Len) of
|
|
0 -> Masked;
|
|
N -> ?BSR(Masked, N)
|
|
end,
|
|
case Len of
|
|
32 -> Unpadded;
|
|
_ -> ?BSL(Unpadded, 32 - Len)
|
|
end;
|
|
bytes_slice(Offs, Len, TotalLen, Bytes) when TotalLen > 32 ->
|
|
%% Bytes is a pointer to memory. The VM can read at non-aligned addresses.
|
|
%% Might read one word more than necessary.
|
|
Word = op('!', Offs, Bytes),
|
|
case Len == 32 of
|
|
true -> Word;
|
|
_ -> ?BSL(?BSR(Word, 32 - Len), 32 - Len)
|
|
end.
|
|
|
|
builtin_bytes_concat(A, B) ->
|
|
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
|
MkBytes = fun([W]) -> W;
|
|
(Ws) -> {tuple, Ws} end,
|
|
Words = fun(N) -> (N + 31) div 32 end,
|
|
WordsRes = Words(A + B),
|
|
Word = fun(I) when 32 * (I + 1) =< A -> bytes_slice(I * 32, 32, A, ?V(a));
|
|
(I) when 32 * I < A ->
|
|
Len = A rem 32,
|
|
Hi = bytes_slice(32 * I, Len, A, ?V(a)),
|
|
Lo = bytes_slice(0, min(32 - Len, B), B, ?V(b)),
|
|
?ADD(Hi, ?BSR(Lo, Len));
|
|
(I) ->
|
|
Offs = 32 * I - A,
|
|
Len = min(32, B - Offs),
|
|
bytes_slice(Offs, Len, B, ?V(b))
|
|
end,
|
|
Body =
|
|
case {A, B} of
|
|
{0, _} -> ?V(b);
|
|
{_, 0} -> ?V(a);
|
|
_ -> MkBytes([ Word(I) || I <- lists:seq(0, WordsRes - 1) ])
|
|
end,
|
|
{[{"a", Type(A)}, {"b", Type(B)}], Body, Type(A + B)}.
|
|
|
|
builtin_bytes_split(A, B) ->
|
|
Type = fun(N) when N =< 32 -> word; (_) -> pointer end,
|
|
MkBytes = fun([W]) -> W;
|
|
(Ws) -> {tuple, Ws} end,
|
|
Word = fun(I, Max) ->
|
|
bytes_slice(I, min(32, Max - I), A + B, ?V(c))
|
|
end,
|
|
Body =
|
|
case {A, B} of
|
|
{0, _} -> [?I(0), ?V(c)];
|
|
{_, 0} -> [?V(c), ?I(0)];
|
|
_ -> [MkBytes([ Word(I, A) || I <- lists:seq(0, A - 1, 32) ]),
|
|
MkBytes([ Word(I, A + B) || I <- lists:seq(A, A + B - 1, 32) ])]
|
|
end,
|
|
{[{"c", Type(A + B)}], {tuple, Body}, {tuple, [Type(A), Type(B)]}}.
|
|
|
|
bytes_to_raw_string(N, Term) when N =< 32 ->
|
|
{tuple, [?I(N), Term]};
|
|
bytes_to_raw_string(N, Term) when N > 32 ->
|
|
Elem = fun(I) -> #binop{op = '!', left = ?I(32 * I), right = ?V(bin)}
|
|
end,
|
|
Words = (N + 31) div 32,
|
|
?LET(bin, Term, {tuple, [?I(N) | [Elem(I) || I <- lists:seq(0, Words - 1)]]}).
|
|
|