%%%------------------------------------------------------------------- %%% @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, <>} = 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) -> <> = <<"123456789ABCDEFGHJKLMNPQRSTUVWXY">>, <> = <<"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() -> <> = <<"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() -> <> = <<"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)]]}).