diff --git a/.gitignore b/.gitignore index 318406e..4ad6576 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ rel/example_project .concrete/DEV_MODE .rebar aeb_asm_scan.erl +aeb_fate_asm_scan.erl _build/ +aefateasm diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1d04708 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ + + +REBAR ?= rebar3 + +all: local + +local: + @$(REBAR) as local release + +console: + @$(REBAR) as local shell + +clean: + @$(REBAR) clean + +distclean: clean + @rm -rf _build/ + +test: local + @$(REBAR) as local eunit diff --git a/include/aeb_fate_data.hrl b/include/aeb_fate_data.hrl new file mode 100644 index 0000000..424c9b1 --- /dev/null +++ b/include/aeb_fate_data.hrl @@ -0,0 +1,55 @@ +-define(FATE_INTEGER_T, integer()). +-define(FATE_BYTE_T, 0..255). +-define(FATE_BOOLEAN_T, true | false). +-define(FATE_NIL_T, []). +-define(FATE_LIST_T, list()). +-define(FATE_UNIT_T, {tuple, {}}). +-define(FATE_MAP_T, #{ fate_type() => fate_type() }). +-define(FATE_STRING_T, binary()). +-define(FATE_ADDRESS_T, {address, <<_:256>>}). +-define(FATE_VARIANT_T, {variant, ?FATE_BYTE_T, ?FATE_BYTE_T, tuple()}). +-define(FATE_VOID_T, void). +-define(FATE_TUPLE_T, {tuple, tuple()}). +-define(FATE_BITS_T, {bits, integer()}). + +-define(IS_FATE_INTEGER(X), is_integer(X)). +-define(IS_FATE_LIST(X), (is_list(X))). +-define(IS_FATE_STRING(X), (is_binary(X))). +-define(IS_FATE_MAP(X), (is_map(X))). +-define(IS_FATE_TUPLE(X), (is_tuple(X) andalso (tuple == element(1, X) andalso is_tuple(element(2, X))))). +-define(IS_FATE_ADDRESS(X), (is_tuple(X) andalso (address == element(1, X) andalso is_binary(element(2, X))))). +-define(IS_FATE_BITS(X), (is_tuple(X) andalso (bits == element(1, X) andalso is_integer(element(2, X))))). +-define(IS_FATE_VARIANT(X), (is_tuple(X) + andalso + (variant == element(1, X) + andalso is_integer(element(2, X)) + andalso is_integer(element(3, X)) + andalso is_tuple(element(4, X)) + ))). +-define(IS_FATE_BOOLEAN(X), is_boolean(X)). + +-define(FATE_UNIT, {tuple, {}}). +-define(FATE_TUPLE(T), {tuple, T}). +-define(FATE_ADDRESS(A), {address, A}). +-define(FATE_BITS(B), {bits, B}). + + +-define(FATE_INTEGER_VALUE(X), (X)). +-define(FATE_LIST_VALUE(X), (X)). +-define(FATE_STRING_VALUE(X), (X)). +-define(FATE_ADDRESS_VALUE(X), (element(2, X))). +-define(FATE_MAP_VALUE(X), (X)). +-define(FATE_MAP_SIZE(X), (map_size(X))). +-define(FATE_STRING_SIZE(X), (byte_size(X))). +-define(FATE_TRUE, true). +-define(FATE_FALSE, false). +-define(FATE_NIL, []). +-define(FATE_VOID, void). +-define(FATE_EMPTY_STRING, <<>>). +-define(FATE_STRING(S), S). +-define(FATE_VARIANT(Size, Tag,T), {variant, Size, Tag, T}). + +-define(MAKE_FATE_INTEGER(X), X). +-define(MAKE_FATE_LIST(X), X). +-define(MAKE_FATE_MAP(X), X). +-define(MAKE_FATE_STRING(X), X). diff --git a/include/aeb_fate_opcodes.hrl b/include/aeb_fate_opcodes.hrl new file mode 100644 index 0000000..90f5b6a --- /dev/null +++ b/include/aeb_fate_opcodes.hrl @@ -0,0 +1,121 @@ + +%% FATE opcodes +-define('NOP' , 16#00). +-define('RETURN' , 16#01). +-define('CALL' , 16#02). +-define('CALL_R' , 16#03). +-define('CALL_T' , 16#04). +-define('CALL_TR' , 16#05). +-define('JUMP' , 16#06). +-define('JUMPIF' , 16#07). +-define('SWITCH' , 16#08). +-define('PUSH' , 16#09). +-define('DUP' , 16#0a). +-define('POP' , 16#0b). +-define('STORE' , 16#10). +-define('ADD' , 16#11). +-define('MUL' , 16#12). +-define('SUB' , 16#13). +-define('DIV' , 16#14). +-define('MOD' , 16#15). +-define('POW' , 16#16). +-define('LT' , 16#17). +-define('GT' , 16#18). +-define('EQ' , 16#19). +-define('ELT' , 16#1a). +-define('EGT' , 16#1b). +-define('NEQ' , 16#1c). +-define('AND' , 16#1d). +-define('OR' , 16#1e). +-define('NOT' , 16#1f). +-define('TUPLE' , 16#20). +-define('ELEMENT' , 16#21). +-define('MAP_EMPTY' , 16#22). +-define('MAP_LOOKUP' , 16#23). +-define('MAP_UPDATE' , 16#24). +-define('MAP_DELETE' , 16#25). +-define('MAP_MEMBER' , 16#26). +-define('MAP_FROM_LIST' , 16#27). +-define('NIL' , 16#28). +-define('IS_NIL' , 16#29). +-define('CONS' , 16#2a). +-define('HD' , 16#2b). +-define('TL' , 16#2c). +-define('LENGTH' , 16#2d). +-define('STR_EQ' , 16#2e). +-define('STR_JOIN' , 16#2f). +-define('ADDR_TO_STR' , 16#30). +-define('STR_REVERSE' , 16#31). +-define('INT_TO_ADDR' , 16#32). +-define('VARIANT' , 16#33). +-define('VARIANT_TEST' , 16#34). +-define('VARIANT_ELEMENT', 16#35). +-define('BITS_NONE' , 16#36). +-define('BITS_ALL' , 16#37). +-define('BITS_SET' , 16#38). +-define('BITS_CLEAR' , 16#39). +-define('BITS_TEST' , 16#3a). +-define('BITS_SUM' , 16#3b). +-define('BITS_OR' , 16#3c). +-define('BITS_AND' , 16#3d). +-define('BITS_DIFF' , 16#3e). +-define('ADDRESS' , 16#3f). +-define('BALANCE' , 16#40). +-define('ORIGIN' , 16#41). +-define('CALLER' , 16#42). +-define('GASPRICE' , 16#43). +-define('BLOCKHASH' , 16#44). +-define('BENEFICIARY' , 16#45). +-define('TIMESTAMP' , 16#46). +-define('NUMBER' , 16#47). +-define('DIFFICULTY' , 16#48). +-define('GASLIMIT' , 16#49). +-define('GAS' , 16#4a). +-define('LOG0' , 16#4b). +-define('LOG1' , 16#4c). +-define('LOG2' , 16#4d). +-define('LOG3' , 16#4e). +-define('LOG4' , 16#4f). +-define('ABORT' , 16#50). +-define('EXIT' , 16#51). +-define('DEACTIVATE' , 16#52). +-define('INC' , 16#53). +-define('DEC' , 16#54). +-define('INT_TO_STR' , 16#55). +-define('SPEND' , 16#56). +-define('ORACLE_REGISTER', 16#57). +-define('ORACLE_QUERY' , 16#58). +-define('ORACLE_RESPOND' , 16#59). +-define('ORACLE_EXTEND' , 16#5a). +-define('ORACLE_GET_ANSWER', 16#5b). +-define('ORACLE_GET_QUESTION', 16#5c). +-define('ORACLE_QUERY_FEE', 16#5d). +-define('AENS_RESOLVE' , 16#5e). +-define('AENS_PRECLAIM' , 16#5f). +-define('AENS_CLAIM' , 16#60). +-define('AENS_UPDATE' , 16#61). +-define('AENS_TRANSFER' , 16#62). +-define('AENS_REVOKE' , 16#63). +-define('ECVERIFY' , 16#64). +-define('SHA3' , 16#65). +-define('SHA256' , 16#66). +-define('BLAKE2B' , 16#67). +-define('RETURNR' , 16#68). +-define('MAP_LOOKUPD' , 16#69). +-define('SWITCH_V2' , 16#6a). +-define('SWITCH_V3' , 16#6b). +-define('SWITCH_VN' , 16#6c). +-define('BITS_ALL_N' , 16#6d). +-define('BITS_NONEA' , 16#6e). +-define('BITS_ALLA' , 16#6f). +-define('DUPA' , 16#70). +-define('INCA' , 16#71). +-define('DECA' , 16#72). +-define('POPA' , 16#73). + + +-define('FUNCTION' , 16#fe). +-define('EXTEND' , 16#ff). + +-define( COMMENT(X), {comment, X}). + diff --git a/rebar.config b/rebar.config index cfd8b45..2b23e8e 100644 --- a/rebar.config +++ b/rebar.config @@ -2,10 +2,42 @@ {erl_opts, [debug_info]}. -{deps, []}. +{deps, [ {getopt, "1.0.1"} + ]}. + +{escript_incl_apps, [aebytecode, getopt]}. +{escript_main_app, aebytecode}. +{escript_name, aefateasm}. +{escript_emu_args, "%%!"}. +{provider_hooks, [{post, [{compile, escriptize}]}]}. + {dialyzer, [ {warnings, [unknown]}, {plt_apps, all_deps}, - {base_plt_apps, [erts, kernel, stdlib]} + {base_plt_apps, [erts, kernel, stdlib, crypto, getopt]} ]}. + + +{relx, [{release, {aessembler, "0.0.1"}, + [aebytecode, getopt]}, + + {dev_mode, true}, + {include_erts, false}, + + {extended_start_script, true}]}. + +{profiles, [{binary, [ + {deps, [ {getopt, "1.0.1"} + ]}, + + {post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)", + escriptize, + "cp \"$REBAR_BUILD_DIR/bin/aefateasm\" ./aefateasm"}, + {"win32", + escriptize, + "robocopy \"%REBAR_BUILD_DIR%/bin/\" ./ aefateasm* " + "/njs /njh /nfl /ndl & exit /b 0"} % silence things + ]} + ]}]}. + diff --git a/rebar.lock b/rebar.lock index 57afcca..4e9f21a 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1 +1,7 @@ -[]. +{"1.1.0", +[ + {<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}. +[ +{pkg_hash,[ + {<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]} +]. diff --git a/src/aeb_blake2.erl b/src/aeb_blake2.erl new file mode 100644 index 0000000..37fda39 --- /dev/null +++ b/src/aeb_blake2.erl @@ -0,0 +1,148 @@ +%%%============================================================================= +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc +%%% BLAKE2b implementation in Erlang - for details see: https://blake2.net +%%% @end +%%%============================================================================= + +-module(aeb_blake2). + +-export([ blake2b/2 + , blake2b/3 + ]). + +-define(MAX_64BIT, 16#ffffffffffffffff). + +-spec blake2b(HashLen :: integer(), Msg :: binary()) -> {ok, binary()}. +blake2b(HashLen, Msg) -> + blake2b(HashLen, Msg, <<>>). + +-spec blake2b(HashLen :: integer(), Msg :: binary(), Key :: binary()) -> {ok, binary()}. +blake2b(HashLen, Msg0, Key) -> + %% If message should be keyed, prepend message with padded key. + Msg = <<(pad(128, Key))/binary, Msg0/binary>>, + + %% Set up the initial state + Init = (16#01010000 + (byte_size(Key) bsl 8) + HashLen), + <> = blake_iv(), + H = <<(H0 bxor Init):64, H1_7/binary>>, + + %% Perform the compression - message will be chopped into 128-byte chunks. + State = blake2b_compress(H, Msg, 0), + + %% Just return the requested part of the hash + {ok, binary_part(to_little_endian(State), {0, HashLen})}. + +blake2b_compress(H, <>, BCompr) when Rest /= <<>> -> + H1 = blake2b_compress(H, <>, BCompr + 128, false), + blake2b_compress(H1, Rest, BCompr + 128); +blake2b_compress(H, SmallChunk, BCompr) -> + Size = byte_size(SmallChunk), + FillSize = (128 - Size) * 8, + blake2b_compress(H, <>, BCompr + Size, true). + +blake2b_compress(H, Chunk0, BCompr, Last) -> + Chunk = to_big_endian(Chunk0), + <> = <>, + V12_ = V12 bxor (BCompr band ?MAX_64BIT), + V13_ = V13 bxor ((BCompr bsr 64) band ?MAX_64BIT), + V14_ = case Last of + false -> V14; + true -> V14 bxor ?MAX_64BIT + end, + V = <>, + + <> = + lists:foldl(fun(Round, Vx) -> blake2b_mix(Round, Chunk, Vx) end, V, lists:seq(0, 11)), + + <> = H, + <<((HInt bxor VLow) bxor VHigh):(8*64)>>. + +blake2b_mix(Rnd, Chunk, V) -> + <> = V, + <> = Chunk, + Ms = {M0, M1, M2, M3, M4, M5, M6, M7, M8, M9, M10, M11, M12, M13, M14, M15}, + M = fun(Ix) -> element(Ix+1, Ms) end, + + [S0, S1, S2, S3, S4, S5, S6, S7, S8, S9, S10, S11, S12, S13, S14, S15] = sigma(Rnd rem 10), + + {Vx0, Vx4, Vx8, Vx12} = blake2b_mix(V0, V4, V8, V12, M(S0), M(S1)), + {Vx1, Vx5, Vx9, Vx13} = blake2b_mix(V1, V5, V9, V13, M(S2), M(S3)), + {Vx2, Vx6, Vx10, Vx14} = blake2b_mix(V2, V6, V10, V14, M(S4), M(S5)), + {Vx3, Vx7, Vx11, Vx15} = blake2b_mix(V3, V7, V11, V15, M(S6), M(S7)), + + {Vy0, Vy5, Vy10, Vy15} = blake2b_mix(Vx0, Vx5, Vx10, Vx15, M(S8), M(S9)), + {Vy1, Vy6, Vy11, Vy12} = blake2b_mix(Vx1, Vx6, Vx11, Vx12, M(S10), M(S11)), + {Vy2, Vy7, Vy8, Vy13} = blake2b_mix(Vx2, Vx7, Vx8, Vx13, M(S12), M(S13)), + {Vy3, Vy4, Vy9, Vy14} = blake2b_mix(Vx3, Vx4, Vx9, Vx14, M(S14), M(S15)), + + <>. + +blake2b_mix(Va, Vb, Vc, Vd, X, Y) -> + Va1 = (Va + Vb + X) band ?MAX_64BIT, + Vd1 = rotr64(32, Vd bxor Va1), + + Vc1 = (Vc + Vd1) band ?MAX_64BIT, + Vb1 = rotr64(24, Vb bxor Vc1), + + Va2 = (Va1 + Vb1 + Y) band ?MAX_64BIT, + Vd2 = rotr64(16, Va2 bxor Vd1), + + Vc2 = (Vc1 + Vd2) band ?MAX_64BIT, + Vb2 = rotr64(63, Vb1 bxor Vc2), + + {Va2, Vb2, Vc2, Vd2}. + +blake_iv() -> + IV0 = 16#6A09E667F3BCC908, + IV1 = 16#BB67AE8584CAA73B, + IV2 = 16#3C6EF372FE94F82B, + IV3 = 16#A54FF53A5F1D36F1, + IV4 = 16#510E527FADE682D1, + IV5 = 16#9B05688C2B3E6C1F, + IV6 = 16#1F83D9ABFB41BD6B, + IV7 = 16#5BE0CD19137E2179, + <>. + +sigma(N) -> + {_, Row} = lists:keyfind(N, 1, sigma()), Row. + +sigma() -> + [{0, [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]}, + {1, [14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3]}, + {2, [11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4]}, + {3, [ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8]}, + {4, [ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13]}, + {5, [ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9]}, + {6, [12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11]}, + {7, [13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10]}, + {8, [ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5]}, + {9, [10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0]}]. + +rotr64(N, I64) -> + <> = rotr641(N, <>), + I64rot. + +rotr641(16, <>) -> <>; +rotr641(24, <>) -> <>; +rotr641(32, <>) -> <>; +rotr641(63, <>) -> <>. + +pad(N, Bin) -> + case (N - (byte_size(Bin) rem N)) rem N of + 0 -> Bin; + Pad -> <> + end. + +to_big_endian(Bin) -> to_big_endian(Bin, <<>>). +to_big_endian(<<>>, Acc) -> Acc; +to_big_endian(<>, Acc) -> + to_big_endian(Rest, <>). + +to_little_endian(Bin) -> to_little_endian(Bin, <<>>). +to_little_endian(<<>>, Acc) -> Acc; +to_little_endian(<>, Acc) -> + to_little_endian(Rest, <>). diff --git a/src/aeb_fate_asm.erl b/src/aeb_fate_asm.erl new file mode 100644 index 0000000..75228b8 --- /dev/null +++ b/src/aeb_fate_asm.erl @@ -0,0 +1,882 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc Assembler for Fate machine code. +%%% +%%% Assembler code can be read from a file. +%%% The assembler has the following format +%%% Comments start with 2 semicolons and runs till end of line +%%% ;; This is a comment +%%% Opcode mnemonics start with an upper case letter. +%%% DUP +%%% Identifiers start with a lower case letter +%%% an_identifier +%%% References to function arguments start with arg +%%% arg0 +%%% References to variables/registers start with var +%%% var0 +%%% Immediates can be of 9 types: +%%% 1. Integers +%%% 42 +%%% -2374683271468723648732648736498712634876147 +%%% 2. Hexadecimal integers starting with 0x +%%% 0x0deadbeef0 +%%% 3. addresses, a 256-bit hash strings starting with # +%%% followed by up to 64 hex chars +%%% #00000deadbeef +%%% 4. Boolean +%%% true +%%% false +%%% 5. Strings +%%% "Hello" +%%% 6. Empty map +%%% {} +%%% 7. Lists +%%% [] +%%% [1, 2] +%%% 8. Bit field +%%% <000> +%%% <1010> +%%% <> +%%% !<> +%%% 9. Tuples +%%% () +%%% (1, "foo") +%%% @end +%%% Created : 21 Dec 2017 +%%%------------------------------------------------------------------- + +-module(aeb_fate_asm). + +-export([ assemble_file/3 + , asm_to_bytecode/2 + , bytecode_to_fate_code/2 + , function_call/1 + , pp/1 + , read_file/1 + , strip/1 + , to_asm/1 + , to_hexstring/1 + ]). + +-include_lib("aebytecode/include/aeb_fate_opcodes.hrl"). +-include_lib("aebytecode/include/aeb_fate_data.hrl"). +-define(HASH_BYTES, 32). + +assemble_file(InFile, OutFile, Options) -> + Asm = read_file(InFile), + {_Env, BC} = asm_to_bytecode(Asm, Options), + ok = file:write_file(OutFile, BC). + +function_call(String) -> + {ok, Tokens, _} = aeb_fate_asm_scan:scan(String), + parse_function_call(Tokens). + +parse_function_call([{id,_,Name}, {'(',_}| Rest]) -> + {Args, []} = to_args(Rest), + aeb_fate_encoding:serialize( + {tuple, {mk_hash(Name), {tuple, list_to_tuple(Args)}}}). + + +to_args([{')', _}]) -> {[], []}; +to_args(Tokens) -> + case to_data(Tokens) of + {Arg, [{',', _} | Rest]} -> + {More, Rest2} = to_args(Rest), + {[Arg|More], Rest2}; + {Arg, [{')', _} | Rest]} -> + {[Arg], Rest} + end. + +to_data([{int,_line, Int}|Rest]) -> + {Int, Rest}; +to_data([{boolean,_line, Bool}|Rest]) -> + {Bool, Rest}; +to_data([{hash,_line, Hash}|Rest]) -> + {Hash, Rest}. + +pp(FateCode) -> + Listing = to_asm(FateCode), + io_lib:format("~ts~n",[Listing]). + + +to_asm(#{ functions := Functions + , symbols := Symbols + , annotations := Annotations} = _FateCode) -> + insert_comments(get_comments(Annotations), 1, + lists:flatten( + io_lib:format("~s", + [format_functions(Functions, Symbols)]))). + +insert_comments([{L,C}|Comments], L, String) -> + ";; " ++ C ++ "\n" ++ insert_comments(Comments, L + 1, String); +insert_comments(Comments, L, [$\n|String]) -> + "\n" ++ insert_comments(Comments, L+1, String); +insert_comments(Comments, L, [C|Rest]) -> + [C|insert_comments(Comments, L, Rest)]; +insert_comments([],_,[]) -> []; +insert_comments([{L,C}|Rest], _, []) -> + ";; " ++ C ++ "\n" ++ insert_comments(Rest, L + 1, []). + + + + + + + +format_functions(Functions, Symbols) -> + [format(lookup(Name, Symbols), + Sig, + lists:sort(maps:to_list(CodeMap)), + Symbols) + || + {Name, {Sig, CodeMap}} <- maps:to_list(Functions)]. + + +format(Name, Sig, BBs, Symbols) -> + [ "FUNCTION " + , Name + , format_sig(Sig) + , "\n" + , format_bbs(BBs, Symbols)]. + +format_sig({Args, RetType}) -> + [ "( " + , format_arg_types(Args) + , ") : " + , format_type(RetType)]. + +format_arg_types([]) -> ""; +format_arg_types([T]) -> format_type(T); +format_arg_types([T|Ts]) -> + [format_type(T) + , ", " + , format_arg_types(Ts)]. + +format_arg({immediate, I}) -> + aeb_fate_data:format(I); +format_arg({arg, N}) -> io_lib:format("arg~p", [N]); +format_arg({var, N}) -> io_lib:format("var~p", [N]); +format_arg({stack, 0}) -> "a"; +format_arg({stack, N}) -> io_lib:format("a~p", [N]). + + +format_type(T) -> + %% TODO: Limit to ok types. + io_lib:format("~p", [T]). + +format_bbs([], _) -> + []; +format_bbs([{BB, Code}|Rest], Symbols) -> + [ io_lib:format(" ;; BB : ~p~n", [BB]) + , format_code(Code, Symbols) + | format_bbs(Rest, Symbols)]. + +format_code([], _) -> + ""; +format_code([Op|Rest], Symbols) -> + [" ", + format_op(Op, Symbols), + "\n", + format_code(Rest, Symbols)]. + +format_op('RETURN', _) -> "RETURN"; +format_op({'RETURNR', Arg}, _) -> ["RETURNR ", format_arg(Arg)]; +format_op({'CALL', {immediate, Function}}, Symbols) -> + ["CALL ", lookup(Function, Symbols)]; +format_op({'CALL_T', {immediate, Function}}, Symbols) -> + ["CALL_T ", lookup(Function, Symbols)]; +format_op({'CALL_R', {immediate, Contract}, {immediate, Function}}, Symbols) -> + ["CALL_R ", lookup(Contract, Symbols), "." , lookup(Function, Symbols)]; +format_op({'CALL_R', Contract, {immediate, Function}}, Symbols) -> + ["CALL_R ", format_arg(Contract), "." , lookup(Function, Symbols)]; +format_op({'CALL_TR', {immediate, Contract}, {immediate, Function}}, Symbols) -> + ["CALL_TR ", lookup(Contract, Symbols), "." , lookup(Function, Symbols)]; +format_op({'CALL_TR', Contract, {immediate, Function}}, Symbols) -> + ["CALL_TR ", format_arg(Contract), "." , lookup(Function, Symbols)]; +format_op({'JUMP', {immediate, BB}}, _) -> + ["JUMP ", io_lib:format("~p", [BB])]; +format_op({'JUMPIF', Arg, {immediate, BB}}, _) -> + ["JUMPIF ", format_arg(Arg), " ", io_lib:format("~p", [BB])]; +format_op({'SWITCH_V2', Variant, {immediate, BB1}, {immediate, BB2}}, _) -> + ["SWITCH_V2 ", format_arg(Variant), " ", BB1, " ", BB2]; +format_op({'SWITCH_V3', Variant, {immediate, BB1}, {immediate, BB2}, {immediate, BB3}}, _) -> + ["SWITCH_V2 ", format_arg(Variant), " ", BB1, " ", BB2, " ", BB3]; +format_op({'SWITCH_VN', Variant, BBs}, _) -> + ["SWITCH_VN ", format_arg(Variant), [[" ", BB] || {immedate, BB} <- BBs]]; +format_op({'PUSH', Arg0}, _) -> + ["PUSH ", format_arg(Arg0)]; +format_op('INCA', _) -> "INCA"; +format_op({'INC', Name}, _) -> ["INC ", format_arg(Name)]; +format_op({'DEC', Name}, _) -> ["DEC ", format_arg(Name)]; +format_op('DECA', _) -> "DECA"; +format_op({'ADD', Dest, Left, Right}, _) -> + ["ADD ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'SUB', Dest, Left, Right}, _) -> + ["SUB ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'MUL', Dest, Left, Right}, _) -> + ["MUL ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'DIV', Dest, Left, Right}, _) -> + ["DIV ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'MOD', Dest, Left, Right}, _) -> + ["MOD ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'POW', Dest, Left, Right}, _) -> + ["POW ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'LT', Dest, Left, Right}, _) -> + ["LT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'GT', Dest, Left, Right}, _) -> + ["GT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'ELT', Dest, Left, Right}, _) -> + ["ELT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'EGT', Dest, Left, Right}, _) -> + ["EGT ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'EQ', Dest, Left, Right}, _) -> + ["EQ ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'NEQ', Dest, Left, Right}, _) -> + ["NEQ ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'AND', Dest, Left, Right}, _) -> + ["AND ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'OR', Dest, Left, Right}, _) -> + ["OR ", format_arg(Dest), " ", format_arg(Left), " ", format_arg(Right)]; +format_op({'NOT', Dest, Name}, _) -> + ["NOT ", format_arg(Dest), " ", format_arg(Name)]; +format_op({'TUPLE', {immediate, Size}}, _) -> + ["TUPLE ", io_lib:format("~p", [Size])]; +format_op({'ELEMENT', Type, Dest, Which, Tuple}, _) -> + [ "ELEMENT " + , io_lib:format("~p ", [Type]) + , format_arg(Dest), " " + , format_arg(Which), " " + , format_arg(Tuple)]; +format_op({'MAP_EMPTY', Dest}, _) -> + ["MAP_EMPTY ", format_arg(Dest)]; +format_op({'MAP_LOOKUP', Dest, Map, Key}, _) -> + ["MAP_LOOKUP ", format_arg(Dest), " " + , format_arg(Map), " ", format_arg(Key)]; +format_op({'MAP_DELETE', Dest, Map, Key}, _) -> + ["MAP_DELETE ", format_arg(Dest), " " + , format_arg(Map), " ", format_arg(Key)]; +format_op({'MAP_LOOKUPD', Dest, Map, Key, Default}, _) -> + ["MAP_LOOKUPD ", format_arg(Dest), " " + , format_arg(Map), " ", format_arg(Key), " ", format_arg(Default)]; +format_op({'MAP_UPDATE', Dest, Map, Key, Value}, _) -> + ["MAP_UPDATE ", format_arg(Dest), " " + , format_arg(Map), " ", format_arg(Key), " ", format_arg(Value)]; +format_op({'MAP_MEMBER', Dest, Map, Key}, _) -> + ["MAP_MEMBER ", format_arg(Dest), " " + , format_arg(Map), " ", format_arg(Key)]; +format_op({'MAP_FROM_LIST', Dest, List}, _) -> + ["MAP_FROM_LIST ", format_arg(Dest), " ", format_arg(List)]; +format_op({'NIL', Dest}, _) -> + ["NIL ", format_arg(Dest)]; +format_op({'IS_NIL', Dest, List}, _) -> + ["IS_NIL ", format_arg(Dest), " ", format_arg(List)]; +format_op({'CONS', Dest, Hd, Tl}, _) -> + ["CONS ", format_arg(Dest), " ", format_arg(Hd), " ", format_arg(Tl)]; +format_op({'HD', Dest, List}, _) -> + ["HD ", format_arg(Dest), " ", format_arg(List)]; +format_op({'TL', Dest, List}, _) -> + ["TL ", format_arg(Dest), " ", format_arg(List)]; +format_op({'LENGTH', Dest, List}, _) -> + ["LENGTH ", format_arg(Dest), " ", format_arg(List)]; +format_op({'STR_EQ', Dest, Str1, Str2}, _) -> + ["STR_EQ ", format_arg(Dest), " ", format_arg(Str1), format_arg(Str2)]; +format_op({'STR_JOIN', Dest, Str1, Str2}, _) -> + ["STR_JOIN ", format_arg(Dest), " ", format_arg(Str1), format_arg(Str2)]; +format_op({'INT_TO_STR', Dest, Str}, _) -> + ["INT_TO_STR ", format_arg(Dest), " ", format_arg(Str)]; +format_op({'ADDR_TO_STR', Dest, Str}, _) -> + ["ADDR_TO_STR ", format_arg(Dest), " ", format_arg(Str)]; +format_op({'STR_REVERSE', Dest, Str}, _) -> + ["STR_REVERSE ", format_arg(Dest), " ", format_arg(Str)]; +format_op({'INT_TO_ADDR', Dest, Str}, _) -> + ["INT_TO_ADDR ", format_arg(Dest), " ", format_arg(Str)]; +format_op({'VARIANT_TEST', Dest, Variant, Tag}, _) -> + ["VARIANT_TEST ", format_arg(Dest), " ", format_arg(Variant), " ", format_arg(Tag)]; +format_op({'VARIANT_ELEMENT', Dest, Variant, Index}, _) -> + ["VARIANT_ELEMENT ", format_arg(Dest), " ", format_arg(Variant), " ", format_arg(Index)]; +format_op({'VARIANT', Dest, SizeA, TagA, ElementsA}, _) -> + ["VARIANT ", format_arg(Dest), " ", format_arg(SizeA), " " + , format_arg(TagA), " ", format_arg(ElementsA)]; +format_op('BITS_NONEA', _) -> "BITS_NONEA "; +format_op({'BITS_NONE', To}, _) -> ["BITS_NONE ", format_arg(To)]; +format_op('BITS_ALLA', _) -> "BITS_ALLA"; +format_op({'BITS_ALL', To}, _) -> ["BITS_ALL ", format_arg(To)]; +format_op({'BITS_ALL_N', To, N}, _) -> + ["BITS_ALL_N ", format_arg(To), " ", format_arg(N)]; +format_op({'BITS_SET', To, Bits, Bit}, _) -> + ["BITS_SET ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; +format_op({'BITS_CLEAR', To, Bits, Bit}, _) -> + ["BITS_CLEAR ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; +format_op({'BITS_TEST', To, Bits, Bit}, _) -> + ["BITS_TEST ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; +format_op({'BITS_SUM', To, Bits}, _) -> + ["BITS_SUM ", format_arg(To), " ", format_arg(Bits)]; +format_op({'BITS_OR', To, Bits, Bit}, _) -> + ["BITS_OR ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; +format_op({'BITS_AND', To, Bits, Bit}, _) -> + ["BITS_AND ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; +format_op({'BITS_DIFF', To, Bits, Bit}, _) -> + ["BITS_DIFF ", format_arg(To), " ", format_arg(Bits), " ", format_arg(Bit)]; +format_op('DUPA', _) -> "DUPA"; +format_op({'DUP', {immediate, N}}, _) -> + ["DUP ", io_lib:format("~p", [N])]; +format_op({'POP', Dest}, _) -> + ["POP ", format_arg(Dest)]; +format_op({'STORE', Var, What}, _) -> + ["STORE ", format_arg(Var), " ", format_arg(What)]; +format_op('NOP', _) -> "NOP". + + +read_file(Filename) -> + {ok, File} = file:read_file(Filename), + binary_to_list(File). + +asm_to_bytecode(AssemblerCode, Options) -> + {ok, Tokens, _} = aeb_fate_asm_scan:scan(AssemblerCode), + + case proplists:lookup(pp_tokens, Options) of + {pp_tokens, true} -> + io:format("Tokens ~p~n",[Tokens]); + none -> + ok + end, + + Env = to_bytecode(Tokens, none, #{ functions => #{} + , symbols => #{} + , annotations => #{} + }, [], Options), + + ByteList = serialize(Env), + Signatures = serialize_sigs(Env), + SymbolTable = serialize_symbol_table(Env), + Annotatations = serialize_annotations(Env), + ByteCode = << (aeb_rlp:encode(list_to_binary(ByteList)))/binary, + (aeb_rlp:encode(list_to_binary(Signatures)))/binary, + (aeb_rlp:encode(SymbolTable))/binary, + (aeb_rlp:encode(Annotatations))/binary + >>, + + case proplists:lookup(pp_hex_string, Options) of + {pp_hex_string, true} -> + io:format("Code: ~s~n",[to_hexstring(ByteList)]); + none -> + ok + end, + + {Env, ByteCode}. + +strip(ByteCode) -> + {Code, _Rest} = aeb_rlp:decode_one(ByteCode), + Code. + +bytecode_to_fate_code(Bytes, _Options) -> + {ByteCode, Rest1} = aeb_rlp:decode_one(Bytes), + {Signatures, Rest2} = aeb_rlp:decode_one(Rest1), + {SymbolTable, Rest3} = aeb_rlp:decode_one(Rest2), + {Annotations, <<>>} = aeb_rlp:decode_one(Rest3), + + Env1 = deserialize(ByteCode, #{ function => none + , bb => 0 + , current_bb_code => [] + , functions => #{} + , code => #{} + }), + Env2 = deserialize_signatures(Signatures, Env1), + Env3 = deserialize_symbols(SymbolTable, Env2), + Env4 = deserialize_annotations(Annotations, Env3), + Env4. + + +deserialize(<>, + #{ function := none + , bb := 0 + , current_bb_code := [] + } = Env) -> + {Sig, Rest2} = deserialize_signature(Rest), + Env2 = Env#{function => {<>, Sig}}, + deserialize(Rest2, Env2); +deserialize(<>, + #{ function := {F, Sig} + , bb := BB + , current_bb_code := Code + , code := Program + , functions := Funs} = Env) -> + {NewSig, Rest2} = deserialize_signature(Rest), + case Code of + [] -> + Env2 = Env#{ bb => 0 + , current_bb_code => [] + , function => {<>, NewSig} + , code => #{} + , functions => Funs#{F => {Sig, Program}}}, + deserialize(Rest2, Env2); + _ -> + Env2 = Env#{ bb => 0 + , current_bb_code => [] + , function => {<>, NewSig} + , code => #{} + , functions => + Funs#{F => {Sig, + Program#{ BB => lists:reverse(Code)}}}}, + deserialize(Rest2, Env2) + end; +deserialize(<>, + #{ bb := BB + , current_bb_code := Code + , code := Program} = Env) -> + {Rest2, OpCode} = deserialize_op(Op, Rest, Code), + case aeb_fate_opcodes:end_bb(Op) of + true -> + deserialize(Rest2, Env#{ bb => BB+1 + , current_bb_code => [] + , code => Program#{BB => + lists:reverse(OpCode)}}); + false -> + deserialize(Rest2, Env#{ current_bb_code => OpCode}) + end; +deserialize(<<>>, #{ function := {F, Sig} + , bb := BB + , current_bb_code := Code + , code := Program + , functions := Funs} = Env) -> + FunctionCode = + case Code of + [] -> Program; + _ -> Program#{ BB => lists:reverse(Code)} + end, + Env#{ bb => 0 + , current_bb_code => [] + , function => none + , code => #{} + , functions => Funs#{F => {Sig, FunctionCode}}}. + +deserialize_op(?ELEMENT, Rest, Code) -> + {Type, Rest2} = deserialize_type(Rest), + <> = Rest2, + {Arg0, Rest4} = aeb_fate_encoding:deserialize_one(Rest3), + {Arg1, Rest5} = aeb_fate_encoding:deserialize_one(Rest4), + {Arg2, Rest6} = aeb_fate_encoding:deserialize_one(Rest5), + Modifier0 = bits_to_modifier(ArgType band 2#11), + Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11), + Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11), + {Rest6, [{ aeb_fate_opcodes:mnemonic(?ELEMENT) + , Type + , {Modifier0, Arg0} + , {Modifier1, Arg1} + , {Modifier2, Arg2}} + | Code]}; +deserialize_op(?SWITCH_VN, Rest, Code) -> + <> = Rest, + {Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2), + case aeb_fate_encoding:deserialize_one(Rest3) of + {N, Rest4} when is_integer(N), N >= 0 -> + Modifier0 = bits_to_modifier(ArgType band 2#11), + immediate = bits_to_modifier((ArgType bsr 2) band 2#11), + {BBs, Rest5} = deserialize_n(N, Rest4), + {Rest5, [{aeb_fate_opcodes:mnemonic(?SWITCH_VN) + , {Modifier0, Arg0} + , {immediate, N} + , list_to_tuple(BBs)} + | Code]}; + _ -> exit(bad_argument_to_switch_vn) + end; +deserialize_op(Op, Rest, Code) -> + OpName = aeb_fate_opcodes:mnemonic(Op), + case aeb_fate_opcodes:args(Op) of + 0 -> {Rest, [OpName | Code]}; + 1 -> + <> = Rest, + {Arg, Rest3} = aeb_fate_encoding:deserialize_one(Rest2), + Modifier = bits_to_modifier(ArgType), + {Rest3, [{OpName, {Modifier, Arg}} | Code]}; + 2 -> + <> = Rest, + {Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2), + {Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3), + Modifier0 = bits_to_modifier(ArgType band 2#11), + Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11), + {Rest4, [{OpName, {Modifier0, Arg0}, + {Modifier1, Arg1}} | Code]}; + 3 -> + <> = Rest, + {Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2), + {Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3), + {Arg2, Rest5} = aeb_fate_encoding:deserialize_one(Rest4), + Modifier0 = bits_to_modifier(ArgType band 2#11), + Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11), + Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11), + {Rest5, [{ OpName + , {Modifier0, Arg0} + , {Modifier1, Arg1} + , {Modifier2, Arg2}} + | Code]}; + 4 -> + <> = Rest, + {Arg0, Rest3} = aeb_fate_encoding:deserialize_one(Rest2), + {Arg1, Rest4} = aeb_fate_encoding:deserialize_one(Rest3), + {Arg2, Rest5} = aeb_fate_encoding:deserialize_one(Rest4), + {Arg3, Rest6} = aeb_fate_encoding:deserialize_one(Rest5), + Modifier0 = bits_to_modifier(ArgType band 2#11), + Modifier1 = bits_to_modifier((ArgType bsr 2) band 2#11), + Modifier2 = bits_to_modifier((ArgType bsr 4) band 2#11), + Modifier3 = bits_to_modifier((ArgType bsr 6) band 2#11), + {Rest6, [{ OpName + , {Modifier0, Arg0} + , {Modifier1, Arg1} + , {Modifier2, Arg2} + , {Modifier3, Arg3}} + | Code]} + end. + +deserialize_n(N, Binary) -> + deserialize_n(N, Binary, []). + +deserialize_n(0, Binary, Acc) -> + {lists:reverse(Acc), Binary}; +deserialize_n(N, Binary, Acc) -> + {Value, Rest} = aeb_fate_encoding:deserialize_one(Binary), + deserialize_n(N-1, Rest, [Value|Acc]). + + + +deserialize_signatures(_Signatures, Env) -> Env. + +deserialize_symbols(Table, Env) -> + ?FATE_MAP_VALUE(SymbolTable) = aeb_fate_encoding:deserialize(Table), + Env#{symbols => SymbolTable}. + +deserialize_annotations(AnnotationsBin, Env) -> + ?FATE_MAP_VALUE(Annotations) = aeb_fate_encoding:deserialize(AnnotationsBin), + Env#{annotations => Annotations}. + + + +serialize_sigs(_Env) -> []. + +serialize_symbol_table(#{ symbols := Symbols }) -> + aeb_fate_encoding:serialize(aeb_fate_data:make_map(Symbols)). + +serialize_annotations(#{ annotations := Annotations}) -> + aeb_fate_encoding:serialize(aeb_fate_data:make_map(Annotations)). + + + + + +serialize(#{functions := Functions} =_Env) -> + %% Sort the functions oon name to get a canonical serialisation. + Code = [[?FUNCTION, Name, serialize_signature(Sig), C] || + {Name, {Sig, C}} <- lists:sort(maps:to_list(Functions))], + serialize_code(lists:flatten(Code)). + + +%% Argument encoding +%% Agument Specification Byte +%% bitpos: 6 4 2 0 +%% xx xx xx xx +%% Arg3 Arg2 Arg1 Arg0 +%% Bit pattern +%% 00 : stack/unused (depending on instruction) +%% 01 : argN +%% 10 : varN +%% 11 : immediate + +serialize_code([ {Arg0Type, Arg0} + , {Arg1Type, Arg1} + , {Arg2Type, Arg2} + , {Arg3Type, Arg3}| Rest]) -> + ArgSpec = + modifier_bits(Arg0Type) bor + (modifier_bits(Arg1Type) bsl 2) bor + (modifier_bits(Arg2Type) bsl 4) bor + (modifier_bits(Arg3Type) bsl 6), + [ ArgSpec + , serialize_data(Arg0Type, Arg0) + , serialize_data(Arg1Type, Arg1) + , serialize_data(Arg2Type, Arg2) + , serialize_data(Arg3Type, Arg3) + | serialize_code(Rest)]; +serialize_code([ {Arg0Type, Arg0} + , {Arg1Type, Arg1} + , {Arg2Type, Arg2} + | Rest]) -> + ArgSpec = + modifier_bits(Arg0Type) bor + (modifier_bits(Arg1Type) bsl 2) bor + (modifier_bits(Arg2Type) bsl 4), + [ArgSpec + , serialize_data(Arg0Type, Arg0) + , serialize_data(Arg1Type, Arg1) + , serialize_data(Arg2Type, Arg2) + | serialize_code(Rest)]; +serialize_code([ {Arg0Type, Arg0} + , {Arg1Type, Arg1} + | Rest]) -> + ArgSpec = + modifier_bits(Arg0Type) bor + (modifier_bits(Arg1Type) bsl 2), + [ArgSpec + , serialize_data(Arg0Type, Arg0) + , serialize_data(Arg1Type, Arg1) + | serialize_code(Rest)]; +serialize_code([ {Arg0Type, Arg0} | Rest]) -> + ArgSpec = + modifier_bits(Arg0Type), + [ArgSpec + , serialize_data(Arg0Type, Arg0) + | serialize_code(Rest)]; +serialize_code([ ?ELEMENT + , ResType + | Rest]) -> + [?ELEMENT, + serialize_type(ResType) + | serialize_code(Rest)]; +serialize_code([ ?SWITCH_VN + , {Arg0Type, Arg0} + , {immediate, N} + | Rest]) when is_integer(N), N >= 0 -> + ArgSpec = + modifier_bits(Arg0Type) bor + (modifier_bits(immediate) bsl 2), + {Serialization, Rest2} = serialize_n_ints(N, Rest), + [?SWITCH_VN + , ArgSpec + , serialize_data(Arg0Type, Arg0) + , serialize_data(immediate, N) + | Serialization] ++ serialize_code(Rest2); +serialize_code([B|Rest]) -> + [B | serialize_code(Rest)]; +serialize_code([]) -> []. + +serialize_n_ints(N, Rest) -> + serialize_n_ints(N, Rest, []). + +serialize_n_ints(0, Rest, Acc) -> + %% Acc is a list of binaries. + {lists:reverse(Acc), Rest}; +serialize_n_ints(N, [Int|Rest], Acc) when is_integer(Int), Int >= 0 -> + serialize_n_ints(N - 1, Rest, [aeb_fate_encoding:serialize(Int)|Acc]); +serialize_n_ints(_, [], _) -> + exit(not_enough_bbs_for_switch_vn); +serialize_n_ints(_, _, _) -> + exit(bad_bbs_value_for_switch_vn). + + + +%% 00 : stack/unused (depending on instruction) +%% 01 : argN +%% 10 : varN +%% 11 : immediate +modifier_bits(immediate) -> 2#11; +modifier_bits(var) -> 2#10; +modifier_bits(arg) -> 2#01; +modifier_bits(stack) -> 2#00. + +bits_to_modifier(2#11) -> immediate; +bits_to_modifier(2#10) -> var; +bits_to_modifier(2#01) -> arg; +bits_to_modifier(2#00) -> stack. + +serialize_data(_, Data) -> + aeb_fate_encoding:serialize(Data). + +serialize_signature({Args, RetType}) -> + [serialize_type({tuple, Args}) | + serialize_type(RetType)]. + + + +deserialize_signature(Binary) -> + {{tuple, Args}, Rest} = deserialize_type(Binary), + {RetType, Rest2} = deserialize_type(Rest), + {{Args, RetType}, Rest2}. + +deserialize_type(<<0, Rest/binary>>) -> {integer, Rest}; +deserialize_type(<<1, Rest/binary>>) -> {boolean, Rest}; +deserialize_type(<<2, Rest/binary>>) -> + {T, Rest2} = deserialize_type(Rest), + {{list, T}, Rest2}; +deserialize_type(<<3, N, Rest/binary>>) -> + {Ts, Rest2} = deserialize_types(N, Rest, []), + {{tuple, Ts}, Rest2}; +deserialize_type(<<4, Rest/binary>>) -> {address, Rest}; +deserialize_type(<<5, Rest/binary>>) -> {bits, Rest}; +deserialize_type(<<6, Rest/binary>>) -> + {K, Rest2} = deserialize_type(Rest), + {V, Rest3} = deserialize_type(Rest2), + {{map, K, V}, Rest3}. + +deserialize_types(0, Binary, Acc) -> + {lists:reverse(Acc), Binary}; +deserialize_types(N, Binary, Acc) -> + {T, Rest} = deserialize_type(Binary), + deserialize_types(N-1, Rest, [T | Acc]). + + +to_hexstring(ByteList) -> + "0x" ++ lists:flatten( + [io_lib:format("~2.16.0b", [X]) + || X <- ByteList]). + + + +%% ------------------------------------------------------------------- +%% Parser +%% Asm tokens -> Fate code env +%% ------------------------------------------------------------------- + +to_bytecode([{function,_line, 'FUNCTION'}|Rest], Address, Env, Code, Opts) -> + Env2 = insert_fun(Address, Code, Env), + {Fun, Rest2} = to_fun_def(Rest), + to_bytecode(Rest2, Fun, Env2, [], Opts); +to_bytecode([{mnemonic,_line, 'ELEMENT'}|Rest], Address, Env, Code, Opts) -> + OpCode = aeb_fate_opcodes:m_to_op('ELEMENT'), + {RetType, Rest2} = to_type(Rest), + to_bytecode(Rest2, Address, Env, [RetType, OpCode|Code], Opts); +to_bytecode([{mnemonic,_line, Op}|Rest], Address, Env, Code, Opts) -> + OpCode = aeb_fate_opcodes:m_to_op(Op), + to_bytecode(Rest, Address, Env, [OpCode|Code], Opts); +to_bytecode([{arg,_line, N}|Rest], Address, Env, Code, Opts) -> + to_bytecode(Rest, Address, Env, [{arg, N}|Code], Opts); +to_bytecode([{var,_line, N}|Rest], Address, Env, Code, Opts) -> + to_bytecode(Rest, Address, Env, [{var, N}|Code], Opts); +to_bytecode([{stack,_line, N}|Rest], Address, Env, Code, Opts) -> + to_bytecode(Rest, Address, Env, [{stack, N}|Code], Opts); +to_bytecode([{int,_line, Int}|Rest], Address, Env, Code, Opts) -> + to_bytecode(Rest, Address, Env, [{immediate, Int}|Code], Opts); +to_bytecode([{boolean,_line, Bool}|Rest], Address, Env, Code, Opts) -> + to_bytecode(Rest, Address, Env, [{immediate, Bool}|Code], Opts); +to_bytecode([{hash,_line, Hash}|Rest], Address, Env, Code, Opts) -> + to_bytecode(Rest, Address, Env, [{immediate, Hash}|Code], Opts); +to_bytecode([{id,_line, ID}|Rest], Address, Env, Code, Opts) -> + {Hash, Env2} = insert_symbol(ID, Env), + to_bytecode(Rest, Address, Env2, [{immediate, Hash}|Code], Opts); +to_bytecode([{comment, Line, Comment}|Rest], Address, Env, Code, Opts) -> + Env2 = insert_annotation(comment, Line, Comment, Env), + to_bytecode(Rest, Address, Env2, Code, Opts); + +to_bytecode([], Address, Env, Code, Opts) -> + Env2 = insert_fun(Address, Code, Env), + #{functions := Funs} = Env2, + case proplists:lookup(pp_opcodes, Opts) of + {pp_opcodes, true} -> + Ops = [C || {_Name, {_Sig, C}} <- maps:to_list(Funs)], + io:format("opcodes ~p~n", [Ops]); + none -> + ok + end, + Env2. + + +to_fun_def([{id, _, Name}, {'(', _} | Rest]) -> + {ArgsType, [{'to', _} | Rest2]} = to_arg_types(Rest), + {RetType, Rest3} = to_type(Rest2), + {{Name, ArgsType, RetType}, Rest3}. + +to_arg_types([{')', _} | Rest]) -> {[], Rest}; +to_arg_types(Tokens) -> + case to_type(Tokens) of + {Type, [{',', _} | Rest]} -> + {MoreTypes, Rest2} = to_arg_types(Rest), + {[Type|MoreTypes], Rest2}; + {Type, [{')', _} | Rest]} -> + {[Type], Rest} + end. + + +%% Type handling + +to_type([{id, _, "integer"} | Rest]) -> {integer, Rest}; +to_type([{id, _, "boolean"} | Rest]) -> {boolean, Rest}; +to_type([{id, _, "string"} | Rest]) -> {string, Rest}; +to_type([{id, _, "address"} | Rest]) -> {address, Rest}; +to_type([{id, _, "bits"} | Rest]) -> {bits, Rest}; +to_type([{'{', _}, {id, _, "list"}, {',', _} | Rest]) -> + %% TODO: Error handling + {ListType, [{'}', _}| Rest2]} = to_type(Rest), + {{list, ListType}, Rest2}; +to_type([{'{', _}, {id, _, "tuple"}, {',', _}, {'[', _} | Rest]) -> + %% TODO: Error handling + {ElementTypes, [{'}', _}| Rest2]} = to_list_of_types(Rest), + {{tuple, ElementTypes}, Rest2}; +to_type([{'{', _}, {id, _, "map"}, {',', _} | Rest]) -> + %% TODO: Error handling + {KeyType, [{',', _}| Rest2]} = to_type(Rest), + {ValueType, [{'}', _}| Rest3]} = to_type(Rest2), + {{map, KeyType, ValueType}, Rest3}. + +to_list_of_types([{']', _} | Rest]) -> {[], Rest}; +to_list_of_types(Tokens) -> + case to_type(Tokens) of + {Type, [{',', _} | Rest]} -> + {MoreTypes, Rest2} = to_list_of_types(Rest), + {[Type|MoreTypes], Rest2}; + {Type, [{']', _} | Rest]} -> + {[Type], Rest} + end. + + +serialize_type(integer) -> [0]; +serialize_type(boolean) -> [1]; +serialize_type({list, T}) -> [2 | serialize_type(T)]; +serialize_type({tuple, Ts}) -> + case length(Ts) of + N when N =< 255 -> + [3, N | [serialize_type(T) || T <- Ts]] + end; +serialize_type(address) -> 4; +serialize_type(bits) -> 5; +serialize_type({map, K, V}) -> [6 | serialize_type(K) ++ serialize_type(V)]. + + +%% ------------------------------------------------------------------- +%% Helper functions +%% ------------------------------------------------------------------- + +%% State handling + +insert_fun(none, [], Env) -> Env; +insert_fun({Name, Type, RetType}, Code, #{functions := Functions} = Env) -> + {Hash, Env2} = insert_symbol(Name, Env), + Env2#{ + functions => Functions#{Hash => {{Type, RetType}, lists:reverse(Code)}} + }. + +mk_hash(Id) -> + %% Use first 4 bytes of blake hash + {ok, <> } = aeb_blake2:blake2b(?HASH_BYTES, list_to_binary(Id)), + <>. + +%% Handle annotations + +insert_annotation(comment, Line, Comment, #{annotations := A} = Env) -> + Key = aeb_fate_data:make_tuple({aeb_fate_data:make_string("comment"), Line}), + Value = aeb_fate_data:make_string(Comment), + Env#{annotations => A#{ Key => Value}}. + +get_comments(Annotations) -> + [ {Line, Comment} || + {?FATE_TUPLE({?FATE_STRING_VALUE("comment"), Line}), + ?FATE_STRING_VALUE(Comment)} <- maps:to_list(Annotations)]. + +%% Symbols handling + +insert_symbol(Id, Env) -> + Hash = mk_hash(Id), + insert_symbol(Id, Hash, Env). + +insert_symbol(Id, Hash, #{symbols := Symbols} = Env) -> + case maps:find(Hash, Symbols) of + {ok, Id} -> {Hash, Env}; + {ok, Id2} -> + %% Very unlikely... + exit({two_symbols_with_same_hash, Id, Id2}); + error -> + {Hash, Env#{symbols => Symbols#{ Id => Hash + , Hash => Id}}} + end. + +%% Symbol table handling + +lookup(Name, Symbols) -> + maps:get(Name, Symbols, Name). diff --git a/src/aeb_fate_asm_scan.xrl b/src/aeb_fate_asm_scan.xrl new file mode 100644 index 0000000..6d10f56 --- /dev/null +++ b/src/aeb_fate_asm_scan.xrl @@ -0,0 +1,207 @@ +%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*- +%%%------------------------------------------------------------------- +%%% @copyright (C) 2019, aeternity Anstalt +%%% @doc +%%% Handling FATE code. +%%% @end +%%% Created : 9 Jan 2019 +%%%------------------------------------------------------------------- + +Definitions. +DIGIT = [0-9] +HEXDIGIT = [0-9a-fA-F] +LOWER = [a-z_] +UPPER = [A-Z] +INT = {DIGIT}+ +HEX = 0x{HEXDIGIT}+ +HASH = #{HEXDIGIT}+ +WS = [\000-\s] +ID = {LOWER}[a-zA-Z0-9_]* + + +Rules. +arg{INT} : {token, {arg, TokenLine, parse_arg(TokenChars)}}. +var{INT} : {token, {var, TokenLine, parse_var(TokenChars)}}. +a : {token, {stack, TokenLine, 0}}. +a{INT} : {token, {stack, TokenLine, parse_acc(TokenChars)}}. + +true : {token, {boolean, TokenLine, true}}. +false : {token, {boolean, TokenLine, false}}. + +RETURN : {token, {mnemonic, TokenLine, 'RETURN'}}. +RETURNR : {token, {mnemonic, TokenLine, 'RETURNR'}}. +CALL : {token, {mnemonic, TokenLine, 'CALL'}}. +NOP : {token, {mnemonic, TokenLine, 'NOP'}}. + +CALL_R : {token, {mnemonic, TokenLine, 'CALL_R'}}. +CALL_T : {token, {mnemonic, TokenLine, 'CALL_T'}}. +CALL_TR : {token, {mnemonic, TokenLine, 'CALL_TR'}}. +JUMP : {token, {mnemonic, TokenLine, 'JUMP'}}. +JUMPIF : {token, {mnemonic, TokenLine, 'JUMPIF'}}. +SWITCH_V2 : {token, {mnemonic, TokenLine, 'SWITCH_V2'}}. +SWITCH_V3 : {token, {mnemonic, TokenLine, 'SWITCH_V3'}}. +SWITCH_VN : {token, {mnemonic, TokenLine, 'SWITCH_VN'}}. + +PUSH : {token, {mnemonic, TokenLine, 'PUSH'}}. +DUP : {token, {mnemonic, TokenLine, 'DUP'}}. +DUPA : {token, {mnemonic, TokenLine, 'DUPA'}}. +POP : {token, {mnemonic, TokenLine, 'POP'}}. + +STORE : {token, {mnemonic, TokenLine, 'STORE'}}. + +ADD : {token, {mnemonic, TokenLine, 'ADD'}}. +MUL : {token, {mnemonic, TokenLine, 'MUL'}}. +SUB : {token, {mnemonic, TokenLine, 'SUB'}}. +DIV : {token, {mnemonic, TokenLine, 'DIV'}}. +MOD : {token, {mnemonic, TokenLine, 'MOD'}}. +POW : {token, {mnemonic, TokenLine, 'POW'}}. + +INC : {token, {mnemonic, TokenLine, 'INC'}}. +DEC : {token, {mnemonic, TokenLine, 'DEC'}}. +INCA : {token, {mnemonic, TokenLine, 'INCA'}}. +DECA : {token, {mnemonic, TokenLine, 'DECA'}}. + +LT : {token, {mnemonic, TokenLine, 'LT'}}. +GT : {token, {mnemonic, TokenLine, 'GT'}}. +EQ : {token, {mnemonic, TokenLine, 'EQ'}}. +ELT : {token, {mnemonic, TokenLine, 'ELT'}}. +EGT : {token, {mnemonic, TokenLine, 'EGT'}}. +NEQ : {token, {mnemonic, TokenLine, 'NEQ'}}. + +AND : {token, {mnemonic, TokenLine, 'AND'}}. +OR : {token, {mnemonic, TokenLine, 'OR'}}. +NOT : {token, {mnemonic, TokenLine, 'NOT'}}. + +TUPLE : {token, {mnemonic, TokenLine, 'TUPLE'}}. +ELEMENT : {token, {mnemonic, TokenLine, 'ELEMENT'}}. + +MAP_EMPTY : {token, {mnemonic, TokenLine, 'MAP_EMPTY'}}. +MAP_LOOKUP : {token, {mnemonic, TokenLine, 'MAP_LOOKUP'}}. +MAP_LOOKUPD : {token, {mnemonic, TokenLine, 'MAP_LOOKUPD'}}. +MAP_UPDATE : {token, {mnemonic, TokenLine, 'MAP_UPDATE'}}. +MAP_MEMBER : {token, {mnemonic, TokenLine, 'MAP_MEMBER'}}. +MAP_DELETE : {token, {mnemonic, TokenLine, 'MAP_DELETE'}}. +MAP_FROM_LIST : {token, {mnemonic, TokenLine, 'MAP_FROM_LIST'}}. + +NIL : {token, {mnemonic, TokenLine, 'NIL'}}. +IS_NIL : {token, {mnemonic, TokenLine, 'IS_NIL'}}. +CONS : {token, {mnemonic, TokenLine, 'CONS'}}. +HD : {token, {mnemonic, TokenLine, 'HD'}}. +TL : {token, {mnemonic, TokenLine, 'TL'}}. +LENGTH : {token, {mnemonic, TokenLine, 'LENGTH'}}. + +STR_EQ : {token, {mnemonic, TokenLine, 'STR_EQ'}}. +STR_JOIN : {token, {mnemonic, TokenLine, 'STR_JOIN'}}. +INT_TO_STR : {token, {mnemonic, TokenLine, 'INT_TO_STR'}}. +ADDR_TO_STR : {token, {mnemonic, TokenLine, 'ADDR_TO_STR'}}. +STR_REVERSE : {token, {mnemonic, TokenLine, 'STR_REVERSE'}}. + +INT_TO_ADDR : {token, {mnemonic, TokenLine, 'INT_TO_ADDR'}}. + +VARIANT : {token, {mnemonic, TokenLine, 'VARIANT'}}. +VARIANT_TEST : {token, {mnemonic, TokenLine, 'VARIANT_TEST'}}. +VARIANT_ELEMENT : {token, {mnemonic, TokenLine, 'VARIANT_ELEMENT'}}. + +BITS_NONE : {token, {mnemonic, TokenLine, 'BITS_NONE'}}. +BITS_NONEA : {token, {mnemonic, TokenLine, 'BITS_NONEA'}}. +BITS_ALL : {token, {mnemonic, TokenLine, 'BITS_ALL'}}. +BITS_ALLA : {token, {mnemonic, TokenLine, 'BITS_ALLA'}}. +BITS_ALL_N : {token, {mnemonic, TokenLine, 'BITS_ALL_N'}}. +BITS_SET : {token, {mnemonic, TokenLine, 'BITS_SET'}}. +BITS_CLEAR : {token, {mnemonic, TokenLine, 'BITS_CLEAR'}}. +BITS_TEST : {token, {mnemonic, TokenLine, 'BITS_TEST'}}. +BITS_SUM : {token, {mnemonic, TokenLine, 'BITS_SUM'}}. +BITS_OR : {token, {mnemonic, TokenLine, 'BITS_OR'}}. +BITS_AND : {token, {mnemonic, TokenLine, 'BITS_AND'}}. +BITS_DIFF : {token, {mnemonic, TokenLine, 'BITS_DIFF'}}. + + +ADDRESS : {token, {mnemonic, TokenLine, 'ADDRESS'}}. +BALANCE : {token, {mnemonic, TokenLine, 'BALANCE'}}. +ORIGIN : {token, {mnemonic, TokenLine, 'ORIGIN'}}. +CALLER : {token, {mnemonic, TokenLine, 'CALLER'}}. +GASPRICE : {token, {mnemonic, TokenLine, 'GASPRICE'}}. +BLOCKHASH : {token, {mnemonic, TokenLine, 'BLOCKHASH'}}. +BENEFICIARY : {token, {mnemonic, TokenLine, 'BENEFICIARY'}}. +TIMESTAMP : {token, {mnemonic, TokenLine, 'TIMESTAMP'}}. +NUMBER : {token, {mnemonic, TokenLine, 'NUMBER'}}. +DIFFICULTY : {token, {mnemonic, TokenLine, 'DIFFICULTY'}}. +GASLIMIT : {token, {mnemonic, TokenLine, 'GASLIMIT'}}. +GAS : {token, {mnemonic, TokenLine, 'GAS'}}. +LOG0 : {token, {mnemonic, TokenLine, 'LOG0'}}. +LOG1 : {token, {mnemonic, TokenLine, 'LOG1'}}. +LOG2 : {token, {mnemonic, TokenLine, 'LOG2'}}. +LOG3 : {token, {mnemonic, TokenLine, 'LOG3'}}. +LOG4 : {token, {mnemonic, TokenLine, 'LOG4'}}. +ABORT : {token, {mnemonic, TokenLine, 'ABORT'}}. +EXIT : {token, {mnemonic, TokenLine, 'EXIT'}}. +DEACTIVATE : {token, {mnemonic, TokenLine, 'DEACTIVATE'}}. +COMMENT : {token, {mnemonic, TokenLine, 'COMMENT'}}. + +FUNCTION : {token, {function, TokenLine, 'FUNCTION' }}. + +{ID} : + {token, {id, TokenLine, TokenChars}}. +{HEX} : + {token, {int, TokenLine, parse_hex(TokenChars)}}. +{INT} : + {token, {int, TokenLine, parse_int(TokenChars)}}. +{HASH} : + {token, {hash, TokenLine, parse_hash(TokenChars)}}. + + +%% Symbols +\-\> : {token, {'to', TokenLine}}. +\: : {token, {'to', TokenLine}}. +, : {token, {',', TokenLine}}. +\( : {token, {'(', TokenLine}}. +\) : {token, {')', TokenLine}}. +\[ : {token, {'[', TokenLine}}. +\] : {token, {']', TokenLine}}. +\{ : {token, {'{', TokenLine}}. +\} : {token, {'}', TokenLine}}. + +;;.* : + {token, {comment, TokenLine, drop_prefix($;, TokenChars)}}. + +\. : skip_token. + + +%% Whitespace ignore +{WS} : skip_token. + +%% Comments (TODO: nested comments) + + +. : {error, "Unexpected token: " ++ TokenChars}. + +Erlang code. + +-export([scan/1]). + +-dialyzer({nowarn_function, yyrev/2}). + +-ignore_xref([format_error/1, string/2, token/2, token/3, tokens/2, tokens/3]). + +-include_lib("aebytecode/include/aeb_fate_opcodes.hrl"). + + +parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16). + +parse_int(Chars) -> list_to_integer(Chars). + +parse_arg("arg" ++ N) -> list_to_integer(N). +parse_var("var" ++ N) -> list_to_integer(N). +parse_acc("a" ++ N) -> list_to_integer(N). + + +parse_hash("#" ++ Chars) -> + N = list_to_integer(Chars, 16), + <>. + +scan(S) -> + string(S). + +drop_prefix(C, [C|Rest]) -> + drop_prefix(C, Rest); +drop_prefix(_, Tail) -> Tail. diff --git a/src/aeb_fate_code.erl b/src/aeb_fate_code.erl new file mode 100644 index 0000000..26c881a --- /dev/null +++ b/src/aeb_fate_code.erl @@ -0,0 +1,303 @@ +-module(aeb_fate_code). + +-export([ return/0 + , return/1 + , call/1 + , call_t/1 + , call_r/2 + , call_tr/2 + , jump/1 + , jumpif/2 + , switch/3 + , switch/4 + , switch/5 + , switch/6 + , push/1 + , inc/0 + , inc/1 + , dec/0 + , dec/1 + , add/3 + , sub/3 + , mul/3 + , divide/3 + , modulo/3 + , pow/3 + , lt/3 + , gt/3 + , elt/3 + , egt/3 + , eq/3 + , neq/3 + , and_op/3 + , or_op/3 + , not_op/2 + , tuple/1 + , element_op/4 + , map_empty/1 + , map_lookup/3 + , map_lookup/4 + , map_update/4 + , map_member/3 + , map_from_list/2 + , nil/1 + , is_nil/2 + , cons/3 + , hd/2 + , tl/2 + , length/2 + , str_eq/3 + , str_join/3 + , int_to_str/2 + , addr_to_str/2 + , str_reverse/2 + , int_to_addr/2 + , variant_test/3 + , variant_element/3 + , variant/4 + , bits_none/0 + , bits_none/1 + , bits_all/0 + , bits_all/1 + , bits_all_n/2 + , bits_set/3 + , bits_clear/3 + , bits_test/3 + , bits_sum/2 + , bits_or/3 + , bits_and/3 + , bits_diff/3 + , dup/0 + , dup/1 + , pop/0 + , store/2 + , nop/0 + ]). + +-define(i(__X__), {immediate, __X__ }). + +return() -> + 'RETURN'. + +return(Arg) -> + {'RETURNR', Arg}. + +call(Function) when is_binary(Function)-> + {'CALL', ?i(Function) }. + +call_t(Function) when is_binary(Function) -> + {'CALL_T', ?i(Function)}. + +call_r(Contract, Function) when is_binary(Function) -> + {'CALL_R', Contract, ?i(Function)}. + +call_tr(Contract, Function) when is_binary(Function) -> + {'CALL_TR', Contract, ?i(Function)}. + +jump(BB) when is_integer(BB) -> + {'JUMP', ?i(BB)}. + +jumpif(Arg, BB) when is_integer(BB) -> + {'JUMPIF', Arg, ?i(BB)}. + +switch(Arg, BB1, BB2) when is_integer(BB1), + is_integer(BB2) -> + {'SWITCH_V2', Arg, ?i(BB1), ?i(BB2)}. + +switch(Arg, BB1, BB2, BB3) when is_integer(BB1), + is_integer(BB2), + is_integer(BB3) -> + {'SWITCH_V3', Arg, ?i(BB1), ?i(BB2), ?i(BB3)}. + +switch(Arg, BB1, BB2, BB3, BB4) when is_integer(BB1), + is_integer(BB2), + is_integer(BB3), + is_integer(BB4) -> + {'SWITCH_V4', Arg, ?i(BB1), ?i(BB2), ?i(BB3), ?i(BB4)}. + +switch(Arg, BB1, BB2, BB3, BB4, BB5) when is_integer(BB1), + is_integer(BB2), + is_integer(BB3), + is_integer(BB4), + is_integer(BB5) -> + {'SWITCH_V5', Arg, ?i(BB1), ?i(BB2), ?i(BB3), ?i(BB4), ?i(BB5)}. + +push(Arg) -> + {'PUSH', Arg}. + +inc() -> + 'INCA'. + +inc(Arg) -> + {'INC', Arg}. + +dec() -> + 'DECA'. + +dec(Arg) -> + {'DEC', Arg}. + +add(Dest, Left, Right) -> + {'ADD', Dest, Left, Right}. + +sub(Dest, Left, Right) -> + {'SUB', Dest, Left, Right}. + +mul(Dest, Left, Right) -> + {'MUL', Dest, Left, Right}. + +divide(Dest, Left, Right) -> + {'DIV', Dest, Left, Right}. + +modulo(Dest, Left, Right) -> + {'MOD', Dest, Left, Right}. + +pow(Dest, Left, Right) -> + {'POW', Dest, Left, Right}. + +lt(Dest, Left, Right) -> + {'LT', Dest, Left, Right}. + +gt(Dest, Left, Right) -> + {'GT', Dest, Left, Right}. + +elt(Dest, Left, Right) -> + {'ELT', Dest, Left, Right}. + +egt(Dest, Left, Right) -> + {'EGT', Dest, Left, Right}. + +eq(Dest, Left, Right) -> + {'EQ', Dest, Left, Right}. + +neq(Dest, Left, Right) -> + {'NEQ', Dest, Left, Right}. + +and_op(Dest, Left, Right) -> + {'AND', Dest, Left, Right}. + +or_op(Dest, Left, Right) -> + {'OR', Dest, Left, Right}. + +not_op(Dest, Arg) -> + {'NOT', Dest, Arg}. + +tuple(Size) when is_integer(Size) -> + {'TUPLE', ?i(Size)}. + +element_op(Type, Dest, N, T) -> + {'ELEMENT', Type, Dest, N, T}. + +map_empty(Dest) -> + {'MAP_EMPTY', Dest}. + +map_lookup(Dest, Map, Key) -> + {'MAP_LOOKUP', Dest, Map, Key}. + +map_lookup(Dest, Map, Key, Default) -> + {'MAP_LOOKUPD', Dest, Map, Key, Default}. + +map_update(Dest, Map, Key, Value) -> + {'MAP_UPDATE', Dest, Map, Key, Value}. + +map_member(Dest, Map, Key) -> + {'MAP_MEMBER', Dest, Map, Key}. + +map_from_list(Dest, List) -> + {'MAP_MEMBER', Dest, List}. + +nil(Dest) -> + {'NIL', Dest}. + +is_nil(Dest, List) -> + {'IS_NIL', Dest, List}. + +cons(Dest, Hd, Tl) -> + {'CONS', Dest, Hd, Tl}. + +hd(Dest, List) -> + {'HD', Dest, List}. + +tl(Dest, List) -> + {'TL', Dest, List}. + +length(Dest, List) -> + {'LENGTH', Dest, List}. + +str_eq(Dest, Str1, Str2) -> + {'STR_EQ', Dest, Str1, Str2}. + +str_join(Dest, Str1, Str2) -> + {'STR_JOIN', Dest, Str1, Str2}. + +int_to_str(Dest, Str) -> + {'INT_TO_STR', Dest, Str}. + +addr_to_str(Dest, Str) -> + {'ADDR_TO_STR', Dest, Str}. + +str_reverse(Dest, Str) -> + {'STR_REVERSE', Dest, Str}. + +int_to_addr(Dest, Str) -> + {'INT_TO_ADDR', Dest, Str}. + +variant_test(Dest, Variant, Tag) -> + {'VARIANT_TEST', Dest, Variant, Tag}. + +variant_element( Dest, Variant, Index) -> + {'VARIANT_ELEMENT', Dest, Variant, Index}. + +variant(Dest, SizeA, TagA, ElementsA) -> + {'VARIANT', Dest, SizeA, TagA, ElementsA}. + +bits_none() -> + 'BITS_NONEA'. + +bits_none(To) -> + {'BITS_NONE', To}. + +bits_all() -> + 'BITS_ALLA'. + +bits_all(To) -> + {'BITS_ALL', To}. + +bits_all_n(To, N) -> + {'BITS_ALL_N', To, N}. + +bits_set(To, Bits, Bit) -> + {'BITS_SET', To, Bits, Bit}. + +bits_clear(To, Bits, Bit) -> + {'BITS_CLEAR', To, Bits, Bit}. + +bits_test(To, Bits, Bit) -> + {'BITS_TEST', To, Bits, Bit}. + +bits_sum(To, Bits) -> + {'BITS_SUM', To, Bits}. + +bits_or(To, Bits, Bit) -> + {'BITS_OR', To, Bits, Bit}. + +bits_and(To, Bits, Bit) -> + {'BITS_AND', To, Bits, Bit}. + +bits_diff(To, Bits, Bit) -> + {'BITS_DIFF', To, Bits, Bit}. + +dup() -> + 'DUPA'. + +dup(N) when is_integer(N) -> + {'DUP', ?i(N)}. + +pop() -> + 'POP'. + +store(Var, What) -> + {'STORE', Var, What}. + +nop() -> + 'NOP'. diff --git a/src/aeb_fate_data.erl b/src/aeb_fate_data.erl new file mode 100644 index 0000000..243b204 --- /dev/null +++ b/src/aeb_fate_data.erl @@ -0,0 +1,182 @@ +%% FATE data representation. +%% +-include("aeb_fate_data.hrl"). + +-module(aeb_fate_data). + +-type fate_integer() :: ?FATE_INTEGER_T. +-type fate_boolean() :: ?FATE_BOOLEAN_T. +-type fate_nil() :: ?FATE_NIL_T. +-type fate_list() :: ?FATE_LIST_T. +-type fate_unit() :: ?FATE_UNIT_T. +-type fate_map() :: ?FATE_MAP_T. +-type fate_string() :: ?FATE_STRING_T. +-type fate_address() :: ?FATE_ADDRESS_T. + +-type fate_variant() :: ?FATE_VARIANT_T. + +-type fate_void() :: ?FATE_VOID_T. + +-type fate_tuple() :: ?FATE_TUPLE_T. + +-type fate_type() :: + fate_boolean() + | fate_integer() + | fate_nil() + | fate_list() + | fate_unit() + | fate_tuple() + | fate_string() + | fate_address() + | fate_variant() + | fate_map() + | fate_list() + | fate_tuple() + | fate_void(). %% Not sure we need this. + +-export_type([fate_type/0]). + +-export([ make_integer/1 + , make_boolean/1 + , make_list/1 + , make_variant/3 + , make_tuple/1 + , make_string/1 + , make_map/1 + , make_address/1 + , make_bits/1 + , make_unit/0 + , tuple_to_list/1 + , decode/1 + , encode/1 + ]). +-export([format/1]). + + +make_integer(I) when is_integer(I) -> ?MAKE_FATE_INTEGER(I). +make_boolean(true) -> ?FATE_TRUE; +make_boolean(false) -> ?FATE_FALSE. +make_list([]) -> ?FATE_NIL; +make_list(L) -> ?MAKE_FATE_LIST(L). +make_string(S) when is_list(S) -> + ?FATE_STRING(list_to_binary(lists:flatten(S))); +make_string(S) when is_binary(S) -> ?FATE_STRING(S). +make_unit() -> ?FATE_UNIT. +make_tuple(T) -> ?FATE_TUPLE(T). +make_map(M) -> ?MAKE_FATE_MAP(M). +make_address(A) -> ?FATE_ADDRESS(A). +make_bits(I) when is_integer(I) -> ?FATE_BITS(I). + +make_variant(Size, Tag, Values) when is_integer(Size), is_integer(Tag) + , 0 =< Size + , 0 =< Tag + , Tag < Size + , is_tuple(Values) -> + ?FATE_VARIANT(Size, Tag, Values). + +tuple_to_list(?FATE_TUPLE(T)) -> erlang:tuple_to_list(T). + +%% Encode is a convinience function for testing, encoding an Erlang term +%% to a Fate term, but it can not distinguish between e.g. 32-byte strings +%% and addresses. Therfore an extra tuple layer on the erlang side for +%% addresses and bits. +encode({bits, Term}) when is_integer(Term) -> make_bits(Term); +%% TODO: check that each byte is in base58 +encode({address, B}) when is_binary(B) -> make_address(B); +encode({address, I}) when is_integer(I) -> B = <>, make_address(B); +encode({address, S}) when is_list(S) -> make_address(base58_to_address(S)); +encode({variant, Size, Tag, Values}) -> make_variant(Size, Tag, Values); +encode(Term) when is_integer(Term) -> make_integer(Term); +encode(Term) when is_boolean(Term) -> make_boolean(Term); +encode(Term) when is_list(Term) -> make_list([encode(E) || E <- Term]); +encode(Term) when is_tuple(Term) -> + make_tuple(list_to_tuple([encode(E) || E <- erlang:tuple_to_list(Term)])); +encode(Term) when is_map(Term) -> + make_map(maps:from_list([{encode(K), encode(V)} || {K,V} <- maps:to_list(Term)])); +encode(Term) when is_binary(Term) -> make_string(Term). + + + +decode(I) when ?IS_FATE_INTEGER(I) -> I; +decode(?FATE_TRUE) -> true; +decode(?FATE_FALSE) -> false; +decode(L) when ?IS_FATE_LIST(L) -> [decode(E) || E <- L]; +decode(?FATE_ADDRESS(<>)) -> {address, Address}; +decode(?FATE_BITS(Bits)) -> {bits, Bits}; +decode(?FATE_TUPLE(T)) -> erlang:list_to_tuple([decode(E) || E <- T]); +decode(?FATE_VARIANT(Size, Tag, Values)) -> {variant, Size, Tag, Values}; +decode(S) when ?IS_FATE_STRING(S) -> binary_to_list(S); +decode(M) when ?IS_FATE_MAP(M) -> + maps:from_list([{decode(K), decode(V)} || {K, V} <- maps:to_list(M)]). + +-spec format(fate_type()) -> iolist(). +format(I) when ?IS_FATE_INTEGER(I) -> integer_to_list(?MAKE_FATE_INTEGER(I)); +format(?FATE_VOID) -> "void"; +format(?FATE_TRUE) -> "true"; +format(?FATE_FALSE) -> "false"; +format(?FATE_NIL) -> "[]"; +format(L) when ?IS_FATE_LIST(L) -> format_list(?FATE_LIST_VALUE(L)); +format(?FATE_UNIT) -> "unit"; +format(?FATE_TUPLE(T)) -> + "{ " ++ [format(E) ++ " " || E <- erlang:tuple_to_list(T)] ++ "}"; +format(S) when ?IS_FATE_STRING(S) -> [S]; +format(?FATE_VARIANT(Size, Tag, T)) -> + "( " ++ integer_to_list(Size) ++ ", " + ++ integer_to_list(Tag) ++ ", " + ++ [format(E) ++ " " || E <- erlang:tuple_to_list(T)] + ++ " )"; +format(M) when ?IS_FATE_MAP(M) -> + "#{ " + ++ format_kvs(maps:to_list(?FATE_MAP_VALUE(M))) + ++" }"; +format(?FATE_ADDRESS(Address)) -> address_to_base58(Address); +format(V) -> exit({not_a_fate_type, V}). + +format_list([]) -> " ]"; +format_list([E]) -> format(E) ++ " ]"; +format_list([H|T]) -> format(H) ++ ", " ++ format_list(T). + +format_kvs([]) -> ""; +format_kvs([{K,V}]) -> "( " ++ format(K) ++ " => " ++ format(V) ++ " )"; +format_kvs([{K,V} | Rest]) -> + "( " ++ format(K) ++ " => " ++ format(V) ++ " ), " ++ format_kvs(Rest). + + +%% -- Local base 58 library + +base58char(Char) -> + binary:at(<<"123456789ABCDEFGHJKLMNPQRSTUVWXYZ" + "abcdefghijkmnopqrstuvwxyz">>, Char). +char_to_base58(C) -> + binary:at(<<0,1,2,3,4,5,6,7,8,0,0,0,0,0,0,0,9,10,11,12,13,14,15,16,0,17, + 18,19,20,21,0,22,23,24,25,26,27,28,29,30,31,32,0,0,0,0,0,0, + 33,34,35,36,37,38,39,40,41,42,43,0,44,45,46,47,48,49,50,51, + 52,53,54,55,56,57>>, C-$1). + +base58_to_integer(C, []) -> C; +base58_to_integer(C, [X | Xs]) -> + base58_to_integer(C * 58 + char_to_base58(X), Xs). + +base58_to_integer([]) -> error; +base58_to_integer([Char]) -> char_to_base58(Char); +base58_to_integer([Char | Str]) -> + base58_to_integer(char_to_base58(Char), Str). + +base58_to_address(Base58) -> + I = base58_to_integer(Base58), + Bin = <>, + Bin. + +address_to_base58(<>) -> + integer_to_base58(A). + +integer_to_base58(0) -> <<"1">>; +integer_to_base58(Integer) -> + Base58String = integer_to_base58(Integer, []), + list_to_binary(Base58String). + +integer_to_base58(0, Acc) -> Acc; +integer_to_base58(Integer, Acc) -> + Quot = Integer div 58, + Rem = Integer rem 58, + integer_to_base58(Quot, [base58char(Rem)|Acc]). diff --git a/src/aeb_fate_encoding.erl b/src/aeb_fate_encoding.erl new file mode 100644 index 0000000..4a5daa2 --- /dev/null +++ b/src/aeb_fate_encoding.erl @@ -0,0 +1,262 @@ +%% Fate data (and instruction) serialization. +%% +%% The FATE serialization has to fullfill the following properties: +%% * There has to be 1 and only 1 byte sequence +%% representing each unique value in FATE. +%% * A valid byte sequence has to be deserializable to a FATE value. +%% * A valid byte sequence must not contain any trailing bytes. +%% * A serialization is a sequence of 8-bit bytes. +%% +%% The serialization function should fullfill the following: +%% * A valid FATE value should be serialized to a byte sequence. +%% * Any other argument, not representing a valid FATE value should +%% throw an exception +%% +%% The deserialization function should fullfill the following: +%% * A valid byte sequence should be deserialized to a valid FATE value. +%% * Any other argument, not representing a valid byte sequence should +%% throw an exception +%% +%% History +%% * First draft of FATE serialization encoding/decoding. +%% Initial experiment with tags +%% * Second draft +%% * FATE data is now defined in aefa_data.erl +%% * Third draft +%% * Added Bit strings +%% +%% TODO: +%% * Make the code production ready. +%% (add tests, document exported functions). +%% * Handle Variant types better. +%% * Handle type representations. +%% * Handle instructions. +%% +%% ------------------------------------------------------------------------ +-module(aeb_fate_encoding). + +-export([ deserialize/1 + , deserialize_one/1 + , serialize/1 + ]). + +-include("aeb_fate_data.hrl"). + +%% Definition of tag scheme. +%% This has to follow the protocol specification. + +-define(SMALL_INT , 2#0). %% sxxxxxx 0 - 6 bit integer with sign bit +%% 1 Set below +-define(LONG_STRING , 2#00000001). %% 000000 01 - RLP encoded array, size >= 64 +-define(SHORT_STRING , 2#01). %% xxxxxx 01 - [bytes], 0 < xxxxxx:size < 64 +%% 11 Set below +-define(SHORT_LIST , 2#0011). %% xxxx 0011 - [encoded elements], 0 < length < 16 +%% xxxx 0111 - FREE (For typedefs in future) +-define(LONG_TUPLE , 2#00001011). %% 0000 1011 - RLP encoded (size - 16) + [encoded elements], +-define(SHORT_TUPLE , 2#1011). %% xxxx 1011 - [encoded elements], 0 < size < 16 +%% 1111 Set below +-define(LONG_LIST , 2#00011111). %% 0001 1111 - RLP encoded (length - 16) + [Elements] +-define(MAP , 2#00101111). %% 0010 1111 - RLP encoded size + [encoded key, encoded value] +-define(EMPTY_TUPLE , 2#00111111). %% 0011 1111 +-define(POS_BITS , 2#01001111). %% 0100 1111 - RLP encoded integer (to be interpreted as bitfield) +-define(EMPTY_STRING , 2#01011111). %% 0101 1111 +-define(POS_BIG_INT , 2#01101111). %% 0110 1111 - RLP encoded (integer - 64) +-define(FALSE , 2#01111111). %% 0111 1111 +%% %% 1000 1111 - FREE (Possibly for bytecode in the future.) +-define(ADDRESS , 2#10011111). %% 1001 1111 - [32 bytes] +-define(VARIANT , 2#10101111). %% 1010 1111 - encoded size + encoded tag + encoded values +-define(NIL , 2#10111111). %% 1011 1111 - Empty list +-define(NEG_BITS , 2#11001111). %% 1100 1111 - RLP encoded integer (infinite 1:s bitfield) +-define(EMPTY_MAP , 2#11011111). %% 1101 1111 +-define(NEG_BIG_INT , 2#11101111). %% 1110 1111 - RLP encoded (integer - 64) +-define(TRUE , 2#11111111). %% 1111 1111 + +-define(SHORT_TUPLE_SIZE, 16). +-define(SHORT_LIST_SIZE , 16). +-define(SMALL_INT_SIZE , 64). +-define(SHORT_STRING_SIZE, 64). + +-define(POS_SIGN, 0). +-define(NEG_SIGN, 1). + + +%% -------------------------------------------------- +%% Serialize +%% Serialized a Fate data value into a sequence of bytes +%% according to the Fate serialization specification. +%% TODO: The type Fate Data is not final yet. +-spec serialize(aeb_fate_data:fate_type()) -> binary(). +serialize(?FATE_TRUE) -> <>; +serialize(?FATE_FALSE) -> <>; +serialize(?FATE_NIL) -> <>; %% ! Untyped +serialize(?FATE_UNIT) -> <>; %% ! Untyped +serialize(M) when ?IS_FATE_MAP(M), ?FATE_MAP_SIZE(M) =:= 0 -> <>; %% ! Untyped +serialize(?FATE_EMPTY_STRING) -> <>; +serialize(I) when ?IS_FATE_INTEGER(I) -> serialize_integer(I); +serialize(?FATE_BITS(Bits)) when is_integer(Bits) -> serialize_bits(Bits); +serialize(String) when ?IS_FATE_STRING(String), + ?FATE_STRING_SIZE(String) > 0, + ?FATE_STRING_SIZE(String) < ?SHORT_STRING_SIZE -> + Size = ?FATE_STRING_SIZE(String), + Bytes = ?FATE_STRING_VALUE(String), + <>; +serialize(String) when ?IS_FATE_STRING(String), + ?FATE_STRING_SIZE(String) > 0, + ?FATE_STRING_SIZE(String) >= ?SHORT_STRING_SIZE -> + Bytes = ?FATE_STRING_VALUE(String), + <>; +serialize(?FATE_ADDRESS(Address)) when is_binary(Address) -> + <>; +serialize(?FATE_TUPLE(T)) when size(T) > 0 -> + S = size(T), + L = tuple_to_list(T), + Rest = << <<(serialize(E))/binary>> || E <- L >>, + if S < ?SHORT_TUPLE_SIZE -> + <>; + true -> + Size = rlp_integer(S - ?SHORT_TUPLE_SIZE), + <> + end; +serialize(L) when ?IS_FATE_LIST(L) -> + [_E|_] = List = ?FATE_LIST_VALUE(L), + S = length(List), + Rest = << <<(serialize(El))/binary>> || El <- List >>, + if S < ?SHORT_LIST_SIZE -> + <>; + true -> + Val = rlp_integer(S - ?SHORT_LIST_SIZE), + <> + end; +serialize(Map) when ?IS_FATE_MAP(Map) -> + L = [{_K,_V}|_] = maps:to_list(?FATE_MAP_VALUE(Map)), + Size = length(L), + %% TODO: check all K same type, and all V same type + %% check K =/= map + Elements = << <<(serialize(K1))/binary, (serialize(V1))/binary>> || {K1,V1} <- L >>, + <>; +serialize(?FATE_VARIANT(Size, Tag, Values)) when 0 =< Size + , Size < 256 + , 0 =< Tag + , Tag < Size -> + <>. + + +%% ----------------------------------------------------- + +rlp_integer(S) when S >= 0 -> + aeb_rlp:encode(binary:encode_unsigned(S)). + +serialize_integer(I) when ?IS_FATE_INTEGER(I) -> + V = ?FATE_INTEGER_VALUE(I), + Abs = abs(V), + Sign = case V < 0 of + true -> ?NEG_SIGN; + false -> ?POS_SIGN + end, + if Abs < ?SMALL_INT_SIZE -> <>; + Sign =:= ?NEG_SIGN -> <>; + Sign =:= ?POS_SIGN -> <> + end. + +serialize_bits(B) when is_integer(B) -> + Abs = abs(B), + Sign = case B < 0 of + true -> ?NEG_SIGN; + false -> ?POS_SIGN + end, + if + Sign =:= ?NEG_SIGN -> <>; + Sign =:= ?POS_SIGN -> <> + end. + +-spec deserialize(binary()) -> aeb_fate_data:fate_type(). +deserialize(B) -> + {T, <<>>} = deserialize2(B), + T. + +deserialize_one(B) -> deserialize2(B). + +deserialize2(<>) -> + {?MAKE_FATE_INTEGER(I), Rest}; +deserialize2(<>) -> + {?MAKE_FATE_INTEGER(-I), Rest}; +deserialize2(<>) -> + {Bint, Rest2} = aeb_rlp:decode_one(Rest), + {?MAKE_FATE_INTEGER(-binary:decode_unsigned(Bint) - ?SMALL_INT_SIZE), + Rest2}; +deserialize2(<>) -> + {Bint, Rest2} = aeb_rlp:decode_one(Rest), + {?MAKE_FATE_INTEGER(binary:decode_unsigned(Bint) + ?SMALL_INT_SIZE), + Rest2}; +deserialize2(<>) -> + {Bint, Rest2} = aeb_rlp:decode_one(Rest), + {?FATE_BITS(-binary:decode_unsigned(Bint)), Rest2}; +deserialize2(<>) -> + {Bint, Rest2} = aeb_rlp:decode_one(Rest), + {?FATE_BITS(binary:decode_unsigned(Bint)), Rest2}; +deserialize2(<>) -> + {String, Rest2} = aeb_rlp:decode_one(Rest), + {?MAKE_FATE_STRING(String), Rest2}; +deserialize2(<>) -> + String = binary:part(Rest, 0, S), + Rest2 = binary:part(Rest, byte_size(Rest), - (byte_size(Rest) - S)), + {?MAKE_FATE_STRING(String), Rest2}; +deserialize2(<>) -> + {A, Rest2} = aeb_rlp:decode_one(Rest), + {?FATE_ADDRESS(A), Rest2}; +deserialize2(<>) -> + {?FATE_TRUE, Rest}; +deserialize2(<>) -> + {?FATE_FALSE, Rest}; +deserialize2(<>) -> + {?FATE_NIL, Rest}; +deserialize2(<>) -> + {?FATE_UNIT, Rest}; +deserialize2(<>) -> + {?MAKE_FATE_MAP(#{}), Rest}; +deserialize2(<>) -> + {?FATE_EMPTY_STRING, Rest}; +deserialize2(<>) -> + {BSize, Rest1} = aeb_rlp:decode_one(Rest), + N = binary:decode_unsigned(BSize) + ?SHORT_TUPLE_SIZE, + {List, Rest2} = deserialize_elements(N, Rest1), + {?FATE_TUPLE(list_to_tuple(List)), Rest2}; +deserialize2(<>) -> + {List, Rest1} = deserialize_elements(S, Rest), + {?FATE_TUPLE(list_to_tuple(List)), Rest1}; +deserialize2(<>) -> + {BLength, Rest1} = aeb_rlp:decode_one(Rest), + Length = binary:decode_unsigned(BLength) + ?SHORT_LIST_SIZE, + {List, Rest2} = deserialize_elements(Length, Rest1), + {?MAKE_FATE_LIST(List), Rest2}; +deserialize2(<>) -> + {List, Rest1} = deserialize_elements(S, Rest), + {?MAKE_FATE_LIST(List), Rest1}; +deserialize2(<>) -> + {BSize, Rest1} = aeb_rlp:decode_one(Rest), + Size = binary:decode_unsigned(BSize), + {List, Rest2} = deserialize_elements(2*Size, Rest1), + Map = insert_kv(List, #{}), + {?MAKE_FATE_MAP(Map), Rest2}; +deserialize2(<>) -> + if Tag > Size -> exit({too_large_tag_in_variant, Tag, Size}); + true -> + {?FATE_TUPLE(T), Rest2} = deserialize2(Rest), + {?FATE_VARIANT(Size, Tag, T), Rest2} + end. + +insert_kv([], M) -> M; +insert_kv([K,V|R], M) -> insert_kv(R, maps:put(K, V, M)). + +deserialize_elements(0, Rest) -> + {[], Rest}; +deserialize_elements(N, Es) -> + {E, Rest} = deserialize2(Es), + {Tail, Rest2} = deserialize_elements(N-1, Rest), + {[E|Tail], Rest2}. diff --git a/src/aeb_fate_opcodes.erl b/src/aeb_fate_opcodes.erl new file mode 100644 index 0000000..84d93f7 --- /dev/null +++ b/src/aeb_fate_opcodes.erl @@ -0,0 +1,363 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2019, Aeternity Anstalt +%%% @doc +%%% Opcodes +%%% @end +%%%------------------------------------------------------------------- + +-module(aeb_fate_opcodes). + +-export([ args/1 + , end_bb/1 + , mnemonic/1 + , m_to_op/1 + , opcode/1 + ]). + +-include_lib("aebytecode/include/aeb_fate_opcodes.hrl"). + + +%%==================================================================== +%% API +%%==================================================================== + +opcode(X) when X >= 0, X =< 255 -> X; +opcode({comment,X}) -> ?COMMENT(X). + +mnemonic(?NOP) -> 'NOP' ; +mnemonic(?RETURN) -> 'RETURN' ; +mnemonic(?CALL) -> 'CALL' ; +mnemonic(?CALL_R) -> 'CALL_R' ; +mnemonic(?CALL_T) -> 'CALL_T' ; +mnemonic(?CALL_TR) -> 'CALL_TR' ; +mnemonic(?JUMP) -> 'JUMP' ; +mnemonic(?JUMPIF) -> 'JUMPIF' ; +mnemonic(?PUSH) -> 'PUSH' ; +mnemonic(?DUP) -> 'DUP' ; +mnemonic(?DUPA) -> 'DUPA' ; +mnemonic(?POP) -> 'POP' ; +mnemonic(?STORE) -> 'STORE' ; +mnemonic(?ADD) -> 'ADD' ; +mnemonic(?MUL) -> 'MUL' ; +mnemonic(?SUB) -> 'SUB' ; +mnemonic(?DIV) -> 'DIV' ; +mnemonic(?MOD) -> 'MOD' ; +mnemonic(?POW) -> 'POW' ; +mnemonic(?LT) -> 'LT' ; +mnemonic(?GT) -> 'GT' ; +mnemonic(?EQ) -> 'EQ' ; +mnemonic(?ELT) -> 'ELT' ; +mnemonic(?EGT) -> 'EGT' ; +mnemonic(?NEQ) -> 'NEQ' ; +mnemonic(?AND) -> 'AND' ; +mnemonic(?OR) -> 'OR' ; +mnemonic(?NOT) -> 'NOT' ; +mnemonic(?TUPLE) -> 'TUPLE' ; +mnemonic(?ELEMENT) -> 'ELEMENT' ; +mnemonic(?MAP_EMPTY) -> 'MAP_EMPTY' ; +mnemonic(?MAP_LOOKUP) -> 'MAP_LOOKUP' ; +mnemonic(?MAP_UPDATE) -> 'MAP_UPDATE' ; +mnemonic(?MAP_DELETE) -> 'MAP_DELETE' ; +mnemonic(?MAP_MEMBER) -> 'MAP_MEMBER' ; +mnemonic(?MAP_FROM_LIST) -> 'MAP_FROM_LIST' ; +mnemonic(?NIL) -> 'NIL' ; +mnemonic(?IS_NIL) -> 'IS_NIL' ; +mnemonic(?CONS) -> 'CONS' ; +mnemonic(?HD) -> 'HD' ; +mnemonic(?TL) -> 'TL' ; +mnemonic(?LENGTH) -> 'LENGTH' ; +mnemonic(?STR_EQ) -> 'STR_EQ' ; +mnemonic(?STR_JOIN) -> 'STR_JOIN' ; +mnemonic(?ADDR_TO_STR) -> 'ADDR_TO_STR' ; +mnemonic(?STR_REVERSE) -> 'STR_REVERSE' ; +mnemonic(?INT_TO_ADDR) -> 'INT_TO_ADDR' ; +mnemonic(?VARIANT) -> 'VARIANT' ; +mnemonic(?VARIANT_TEST) -> 'VARIANT_TEST' ; +mnemonic(?VARIANT_ELEMENT) -> 'VARIANT_ELEMENT' ; +mnemonic(?BITS_NONE) -> 'BITS_NONE' ; +mnemonic(?BITS_NONEA) -> 'BITS_NONEA' ; +mnemonic(?BITS_ALL) -> 'BITS_ALL' ; +mnemonic(?BITS_ALLA) -> 'BITS_ALLA' ; +mnemonic(?BITS_SET) -> 'BITS_SET' ; +mnemonic(?BITS_CLEAR) -> 'BITS_CLEAR' ; +mnemonic(?BITS_TEST) -> 'BITS_TEST' ; +mnemonic(?BITS_SUM) -> 'BITS_SUM' ; +mnemonic(?BITS_OR) -> 'BITS_OR' ; +mnemonic(?BITS_AND) -> 'BITS_AND' ; +mnemonic(?BITS_DIFF) -> 'BITS_DIFF' ; +mnemonic(?ADDRESS) -> 'ADDRESS' ; +mnemonic(?BALANCE) -> 'BALANCE' ; +mnemonic(?ORIGIN) -> 'ORIGIN' ; +mnemonic(?CALLER) -> 'CALLER' ; +mnemonic(?GASPRICE) -> 'GASPRICE' ; +mnemonic(?BLOCKHASH) -> 'BLOCKHASH' ; +mnemonic(?BENEFICIARY) -> 'BENEFICIARY' ; +mnemonic(?TIMESTAMP) -> 'TIMESTAMP' ; +mnemonic(?NUMBER) -> 'NUMBER' ; +mnemonic(?DIFFICULTY) -> 'DIFFICULTY' ; +mnemonic(?GASLIMIT) -> 'GASLIMIT' ; +mnemonic(?GAS) -> 'GAS' ; +mnemonic(?LOG0) -> 'LOG0' ; +mnemonic(?LOG1) -> 'LOG1' ; +mnemonic(?LOG2) -> 'LOG2' ; +mnemonic(?LOG3) -> 'LOG3' ; +mnemonic(?LOG4) -> 'LOG4' ; +mnemonic(?ABORT) -> 'ABORT' ; +mnemonic(?EXIT) -> 'EXIT' ; +mnemonic(?DEACTIVATE) -> 'DEACTIVATE' ; +mnemonic(?INC) -> 'INC' ; +mnemonic(?DEC) -> 'DEC' ; +mnemonic(?INCA) -> 'INCA' ; +mnemonic(?DECA) -> 'DECA' ; +mnemonic(?INT_TO_STR) -> 'INT_TO_STR' ; +mnemonic(?SPEND) -> 'SPEND' ; +mnemonic(?ORACLE_REGISTER) -> 'ORACLE_REGISTER' ; +mnemonic(?ORACLE_QUERY) -> 'ORACLE_QUERY' ; +mnemonic(?ORACLE_RESPOND) -> 'ORACLE_RESPOND' ; +mnemonic(?ORACLE_EXTEND) -> 'ORACLE_EXTEND' ; +mnemonic(?ORACLE_GET_ANSWER) -> 'ORACLE_GET_ANSWER' ; +mnemonic(?ORACLE_GET_QUESTION) -> 'ORACLE_GET_QUESTION' ; +mnemonic(?ORACLE_QUERY_FEE) -> 'ORACLE_QUERY_FEE' ; +mnemonic(?AENS_RESOLVE) -> 'AENS_RESOLVE' ; +mnemonic(?AENS_PRECLAIM) -> 'AENS_PRECLAIM' ; +mnemonic(?AENS_CLAIM) -> 'AENS_CLAIM' ; +mnemonic(?AENS_UPDATE) -> 'AENS_UPDATE' ; +mnemonic(?AENS_TRANSFER) -> 'AENS_TRANSFER' ; +mnemonic(?AENS_REVOKE) -> 'AENS_REVOKE' ; +mnemonic(?ECVERIFY) -> 'ECVERIFY' ; +mnemonic(?SHA3) -> 'SHA3' ; +mnemonic(?SHA256) -> 'SHA256' ; +mnemonic(?BLAKE2B) -> 'BLAKE2B' ; +mnemonic(?RETURNR) -> 'RETURNR' ; +mnemonic(?MAP_LOOKUPD) -> 'MAP_LOOKUPD' ; +mnemonic(?SWITCH_V2) -> 'SWITCH_V2' ; +mnemonic(?SWITCH_V3) -> 'SWITCH_V3' ; +mnemonic(?SWITCH_VN) -> 'SWITCH_VN' ; +mnemonic(?BITS_ALL_N) -> 'BITS_ALL_N' ; +mnemonic(?FUNCTION) -> 'FUNCTION' ; +mnemonic(?EXTEND) -> 'EXTEND'. + + +m_to_op('NOP') -> ?NOP ; +m_to_op('RETURN') -> ?RETURN ; +m_to_op('CALL') -> ?CALL ; +m_to_op('CALL_R') -> ?CALL_R ; +m_to_op('CALL_T') -> ?CALL_T ; +m_to_op('CALL_TR') -> ?CALL_TR ; +m_to_op('JUMP') -> ?JUMP ; +m_to_op('JUMPIF') -> ?JUMPIF ; +m_to_op('PUSH') -> ?PUSH ; +m_to_op('DUP') -> ?DUP ; +m_to_op('DUPA') -> ?DUPA ; +m_to_op('POP') -> ?POP ; +m_to_op('STORE') -> ?STORE ; +m_to_op('ADD') -> ?ADD ; +m_to_op('MUL') -> ?MUL ; +m_to_op('SUB') -> ?SUB ; +m_to_op('DIV') -> ?DIV ; +m_to_op('MOD') -> ?MOD ; +m_to_op('POW') -> ?POW ; +m_to_op('LT') -> ?LT ; +m_to_op('GT') -> ?GT ; +m_to_op('EQ') -> ?EQ ; +m_to_op('ELT') -> ?ELT ; +m_to_op('EGT') -> ?EGT ; +m_to_op('NEQ') -> ?NEQ ; +m_to_op('AND') -> ?AND ; +m_to_op('OR') -> ?OR ; +m_to_op('NOT') -> ?NOT ; +m_to_op('TUPLE') -> ?TUPLE ; +m_to_op('ELEMENT') -> ?ELEMENT ; +m_to_op('MAP_EMPTY') -> ?MAP_EMPTY ; +m_to_op('MAP_LOOKUP') -> ?MAP_LOOKUP ; +m_to_op('MAP_UPDATE') -> ?MAP_UPDATE ; +m_to_op('MAP_DELETE') -> ?MAP_DELETE ; +m_to_op('MAP_MEMBER') -> ?MAP_MEMBER ; +m_to_op('MAP_FROM_LIST') -> ?MAP_FROM_LIST ; +m_to_op('NIL') -> ?NIL ; +m_to_op('IS_NIL') -> ?IS_NIL ; +m_to_op('CONS') -> ?CONS ; +m_to_op('HD') -> ?HD ; +m_to_op('TL') -> ?TL ; +m_to_op('LENGTH') -> ?LENGTH ; +m_to_op('STR_EQ') -> ?STR_EQ ; +m_to_op('STR_JOIN') -> ?STR_JOIN ; +m_to_op('ADDR_TO_STR') -> ?ADDR_TO_STR ; +m_to_op('STR_REVERSE') -> ?STR_REVERSE ; +m_to_op('INT_TO_ADDR') -> ?INT_TO_ADDR ; +m_to_op('VARIANT') -> ?VARIANT ; +m_to_op('VARIANT_TEST') -> ?VARIANT_TEST ; +m_to_op('VARIANT_ELEMENT') -> ?VARIANT_ELEMENT ; +m_to_op('BITS_NONEA') -> ?BITS_NONEA ; +m_to_op('BITS_ALL') -> ?BITS_ALL ; +m_to_op('BITS_ALLA') -> ?BITS_ALLA ; +m_to_op('BITS_SET') -> ?BITS_SET ; +m_to_op('BITS_CLEAR') -> ?BITS_CLEAR ; +m_to_op('BITS_TEST') -> ?BITS_TEST ; +m_to_op('BITS_SUM') -> ?BITS_SUM ; +m_to_op('BITS_OR') -> ?BITS_OR ; +m_to_op('BITS_AND') -> ?BITS_AND ; +m_to_op('BITS_DIFF') -> ?BITS_DIFF ; +m_to_op('ADDRESS') -> ?ADDRESS ; +m_to_op('BALANCE') -> ?BALANCE ; +m_to_op('ORIGIN') -> ?ORIGIN ; +m_to_op('CALLER') -> ?CALLER ; +m_to_op('GASPRICE') -> ?GASPRICE ; +m_to_op('BLOCKHASH') -> ?BLOCKHASH ; +m_to_op('BENEFICIARY') -> ?BENEFICIARY ; +m_to_op('TIMESTAMP') -> ?TIMESTAMP ; +m_to_op('NUMBER') -> ?NUMBER ; +m_to_op('DIFFICULTY') -> ?DIFFICULTY ; +m_to_op('GASLIMIT') -> ?GASLIMIT ; +m_to_op('GAS') -> ?GAS ; +m_to_op('LOG0') -> ?LOG0 ; +m_to_op('LOG1') -> ?LOG1 ; +m_to_op('LOG2') -> ?LOG2 ; +m_to_op('LOG3') -> ?LOG3 ; +m_to_op('LOG4') -> ?LOG4 ; +m_to_op('ABORT') -> ?ABORT ; +m_to_op('EXIT') -> ?EXIT ; +m_to_op('DEACTIVATE') -> ?DEACTIVATE ; +m_to_op('INC') -> ?INC ; +m_to_op('DEC') -> ?DEC ; +m_to_op('INCA') -> ?INCA ; +m_to_op('DECA') -> ?DECA ; +m_to_op('INT_TO_STR') -> ?INT_TO_STR ; +m_to_op('SPEND') -> ?SPEND ; +m_to_op('ORACLE_REGISTER') -> ?ORACLE_REGISTER ; +m_to_op('ORACLE_QUERY') -> ?ORACLE_QUERY ; +m_to_op('ORACLE_RESPOND') -> ?ORACLE_RESPOND ; +m_to_op('ORACLE_EXTEND') -> ?ORACLE_EXTEND ; +m_to_op('ORACLE_GET_ANSWER') -> ?ORACLE_GET_ANSWER ; +m_to_op('ORACLE_GET_QUESTION') -> ?ORACLE_GET_QUESTION ; +m_to_op('ORACLE_QUERY_FEE') -> ?ORACLE_QUERY_FEE ; +m_to_op('AENS_RESOLVE') -> ?AENS_RESOLVE ; +m_to_op('AENS_PRECLAIM') -> ?AENS_PRECLAIM ; +m_to_op('AENS_CLAIM') -> ?AENS_CLAIM ; +m_to_op('AENS_UPDATE') -> ?AENS_UPDATE ; +m_to_op('AENS_TRANSFER') -> ?AENS_TRANSFER ; +m_to_op('AENS_REVOKE') -> ?AENS_REVOKE ; +m_to_op('ECVERIFY') -> ?ECVERIFY ; +m_to_op('SHA3') -> ?SHA3 ; +m_to_op('SHA256') -> ?SHA256 ; +m_to_op('BLAKE2B') -> ?BLAKE2B ; +m_to_op('RETURNR') -> ?RETURNR ; +m_to_op('MAP_LOOKUPD') -> ?MAP_LOOKUPD ; +m_to_op('SWITCH_V2') -> ?SWITCH_V2 ; +m_to_op('SWITCH_V3') -> ?SWITCH_V3 ; +m_to_op('SWITCH_VN') -> ?SWITCH_VN ; +m_to_op('FUNCTION') -> ?FUNCTION ; +m_to_op('EXTEND') -> ?EXTEND. + + + +args(?NOP) -> 0; +args(?RETURN) -> 0; +args(?INCA) -> 0; +args(?DECA) -> 0; +args(?DUPA) -> 0; +args(?BITS_NONEA) -> 0; +args(?BITS_ALLA) -> 0; + +args(?INC) -> 1; +args(?DEC) -> 1; +args(?RETURNR) -> 1; +args(?PUSH) -> 1; +args(?JUMP) -> 1; +args(?CALL) -> 1; +args(?CALL_T) -> 1; +args(?TUPLE) -> 1; +args(?MAP_EMPTY) -> 1; +args(?DUP) -> 1; +args(?POP) -> 1; +args(?NIL) -> 1; +args(?BITS_NONE) -> 1; +args(?BITS_ALL) -> 1; +args(?ADDRESS) -> 1; +args(?BALANCE) -> 1; +args(?ORIGIN) -> 1; +args(?CALLER) -> 1; +args(?GASPRICE) -> 1; +args(?BLOCKHASH) -> 1; +args(?BENEFICIARY) -> 1; +args(?TIMESTAMP) -> 1; +args(?NUMBER) -> 1; +args(?DIFFICULTY)-> 1; +args(?GASLIMIT) -> 1; +args(?GAS) -> 1; +args(?ABORT) -> 1; +args(?EXIT) -> 1; + +args(?JUMPIF) -> 2; +args(?CALL_R) -> 2; +args(?CALL_TR) -> 2; +args(?HD) -> 2; +args(?TL) -> 2; +args(?NOT) -> 2; +args(?STORE) -> 2; +args(?LENGTH) -> 2; +args(?IS_NIL) -> 2; +args(?BITS_SUM) -> 2; +args(?BITS_ALL_N) -> 2; +args(?ADDR_TO_STR) -> 2; +args(?STR_REVERSE) -> 2; +args(?INT_TO_ADDR) -> 2; +args(?MAP_FROM_LIST) -> 2; + + +args(?ADD) -> 3; +args(?SUB) -> 3; +args(?MUL) -> 3; +args(?DIV) -> 3; +args(?MOD) -> 3; +args(?POW) -> 3; +args(?AND) -> 3; +args(?OR) -> 3; +args(?LT) -> 3; +args(?GT) -> 3; +args(?EGT) -> 3; +args(?ELT) -> 3; +args(?EQ) -> 3; +args(?NEQ) -> 3; +args(?CONS) -> 3; +args(?STR_EQ) -> 3; +args(?STR_JOIN) -> 3; +args(?MAP_MEMBER) -> 3; +args(?MAP_LOOKUP) -> 3; +args(?MAP_DELETE) -> 3; +args(?BITS_OR) -> 3; +args(?BITS_AND) -> 3; +args(?BITS_SET) -> 3; +args(?BITS_DIFF) -> 3; +args(?BITS_TEST) -> 3; +args(?BITS_CLEAR) -> 3; +args(?VARIANT_TEST) -> 3; +args(?VARIANT_ELEMENT) -> 3; +args(?INT_TO_STR) -> 3; +args(?SWITCH_V2) -> 3; + +args(?SWITCH_V3) -> 4; +args(?ELEMENT) -> 4; +args(?VARIANT) -> 4; +args(?MAP_UPDATE) -> 4; +args(?MAP_LOOKUPD) -> 4; + +args(?SWITCH_VN) -> 2; + +args(_) -> 0. %% TODO do not allow this + +end_bb(?RETURN) -> true; +end_bb(?RETURNR) -> true; +end_bb(?JUMP) -> true; +end_bb(?JUMPIF) -> true; +end_bb(?CALL) -> true; +end_bb(?CALL_T) -> true; +end_bb(?CALL_R) -> true; +end_bb(?CALL_TR) -> true; +end_bb(?SWITCH_V2) -> true; +end_bb(?SWITCH_V3) -> true; +end_bb(?SWITCH_VN) -> true; +end_bb(?ABORT) -> true; +end_bb(?EXIT) -> true; + +end_bb(_) -> false. diff --git a/src/aeb_rlp.erl b/src/aeb_rlp.erl new file mode 100644 index 0000000..46a27fa --- /dev/null +++ b/src/aeb_rlp.erl @@ -0,0 +1,91 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2017, Aeternity Anstalt +%%% @doc +%%% Implementation of the Recursive Length Prefix. +%%% +%%% https://github.com/ethereum/wiki/wiki/RLP +%%% +%%% @end +%%%------------------------------------------------------------------- + +-module(aeb_rlp). +-export([ decode/1 + , decode_one/1 + , encode/1 + ]). + +-export_type([ encodable/0 + , encoded/0 + ]). + +-type encodable() :: [encodable()] | binary(). +-type encoded() :: <<_:8, _:_*8>>. + +-define(UNTAGGED_SIZE_LIMIT , 55). +-define(UNTAGGED_LIMIT , 127). +-define(BYTE_ARRAY_OFFSET , 128). +-define(LIST_OFFSET , 192). + + +-spec encode(encodable()) -> encoded(). +encode(X) -> + encode(X, []). + +encode(<> = X,_Opts) when B =< ?UNTAGGED_LIMIT -> + %% An untagged value + X; +encode(X,_Opts) when is_binary(X) -> + %% Byte array + add_size(?BYTE_ARRAY_OFFSET, X); +encode(L, Opts) when is_list(L) -> + %% Lists items are encoded and concatenated + ByteArray = << << (encode(X, Opts))/binary >> || X <- L >>, + add_size(?LIST_OFFSET, ByteArray). + +add_size(Offset, X) when byte_size(X) =< ?UNTAGGED_SIZE_LIMIT -> + %% The size fits in one tagged byte + <<(Offset + byte_size(X)), X/binary>>; +add_size(Offset, X) when is_binary(X) -> + %% The size itself needs to be encoded as a byte array + %% Add the tagged size of the size byte array + SizeBin = binary:encode_unsigned(byte_size(X)), + TaggedSize = ?UNTAGGED_SIZE_LIMIT + Offset + byte_size(SizeBin), + true = (TaggedSize < 256 ), %% Assert + <>. + +-spec decode(encoded()) -> encodable(). +decode(Bin) when is_binary(Bin), byte_size(Bin) > 0 -> + case decode_one(Bin) of + {X, <<>>} -> X; + {X, Left} -> error({trailing, X, Bin, Left}) + end. + +decode_one(<>) when X =< ?UNTAGGED_LIMIT -> + %% Untagged value + {<>, B}; +decode_one(<> = B) when L < ?LIST_OFFSET -> + %% Byte array + {Size, Rest} = decode_size(B, ?BYTE_ARRAY_OFFSET), + <> = Rest, + {X, Tail}; +decode_one(<<_/binary>> = B) -> + %% List + {Size, Rest} = decode_size(B, ?LIST_OFFSET), + <> = Rest, + {decode_list(X), Tail}. + +decode_size(<>, Offset) when L =< Offset + ?UNTAGGED_SIZE_LIMIT-> + %% One byte tagged size. + {L - Offset, B}; +decode_size(<<_, 0, _/binary>>,_Offset) -> + error(leading_zeroes_in_size); +decode_size(<>, Offset) -> + %% Actual size is in a byte array. + BinSize = L - Offset - ?UNTAGGED_SIZE_LIMIT, + <> = B, + {Size, Rest}. + +decode_list(<<>>) -> []; +decode_list(B) -> + {Element, Rest} = decode_one(B), + [Element|decode_list(Rest)]. diff --git a/src/aebytecode.app.src b/src/aebytecode.app.src index 53b095c..e8c0021 100644 --- a/src/aebytecode.app.src +++ b/src/aebytecode.app.src @@ -1,10 +1,11 @@ {application, aebytecode, - [{description, "Bytecode definitions for AEthernity VM shared with compiler."}, - {vsn, "1.0.0"}, + [{description, "Bytecode definitions, serialization and deserialization for aeternity."}, + {vsn, "2.0.0"}, {registered, []}, {applications, [kernel, - stdlib + stdlib, + getopt ]}, {env,[]}, {modules, []}, diff --git a/src/aefateasm.erl b/src/aefateasm.erl new file mode 100644 index 0000000..12183ce --- /dev/null +++ b/src/aefateasm.erl @@ -0,0 +1,57 @@ +-module(aefateasm). + +-export([main/1]). + +-define(OPT_SPEC, + [ {src_file, undefined, undefined, string, "Fate assembler code file"} + , {verbose, $v, "verbose", undefined, "Verbose output"} + , {help, $h, "help", undefined, "Show this message"} + , {outfile, $o, "out", string, "Output file (experimental)"} ]). + +usage() -> + getopt:usage(?OPT_SPEC, "aefateasm"). + +main(Args) -> + case getopt:parse(?OPT_SPEC, Args) of + {ok, {Opts, []}} -> + case proplists:get_value(help, Opts, false) of + false -> + assemble(Opts); + true -> + usage() + end; + + {ok, {_, NonOpts}} -> + io:format("Can't understand ~p\n\n", [NonOpts]), + usage(); + + {error, {Reason, Data}} -> + io:format("Error: ~s ~p\n\n", [Reason, Data]), + usage() + end. + +assemble(Opts) -> + case proplists:get_value(src_file, Opts, undefined) of + undefined -> + io:format("Error: no input source file\n\n"), + usage(); + File -> + assemble(File, Opts) + end. + +assemble(File, Opts) -> + Verbose = proplists:get_value(verbose, Opts, false), + case proplists:get_value(outfile, Opts, undefined) of + undefined -> + Asm = aeb_fate_asm:read_file(File), + {Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, Opts), + case Verbose of + true -> + io:format("Env: ~0p~n", [Env]); + false -> ok + end, + io:format("Code: ~0p~n", [BC]); + OutFile -> + aeb_fate_asm:assemble_file(File, OutFile, Opts) + end. + diff --git a/test/aeb_data_test.erl b/test/aeb_data_test.erl new file mode 100644 index 0000000..7c25932 --- /dev/null +++ b/test/aeb_data_test.erl @@ -0,0 +1,12 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%% @doc Basic tests for Fate data +%%% @end +%%%------------------------------------------------------------------- + +-module(aeb_data_test). + +-include_lib("eunit/include/eunit.hrl"). + +format_integer_test() -> + "0" = aeb_fate_data:format(0). diff --git a/test/aeb_fate_asm_test.erl b/test/aeb_fate_asm_test.erl new file mode 100644 index 0000000..e177c6c --- /dev/null +++ b/test/aeb_fate_asm_test.erl @@ -0,0 +1,65 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%% @doc Basic tests for Fate serialization +%%% +%%% To run: +%%% TEST=aeb_fate_asm_test rebar3 eunit +%%% +%%% @end +%%%------------------------------------------------------------------- + +-module(aeb_fate_asm_test). + +-include_lib("eunit/include/eunit.hrl"). + +asm_path() -> + filename:join(code:lib_dir(aebytecode, test), "asm_code"). + + +file_path(File) -> + filename:join(asm_path(), File) ++ ".fate". + +read_file(File) -> + FilePath = file_path(File), + Asm = aeb_fate_asm:read_file(FilePath), + Asm. + +assemble(Asm) -> + {Env, BC} = aeb_fate_asm:asm_to_bytecode(Asm, []), + {Env, BC}. + +disassemble(BC) -> + aeb_fate_asm:bytecode_to_fate_code(BC, []). + + +asm_disasm_idenity_test() -> + check_roundtrip(identity). + +asm_disasm_files_test_() -> + [{lists:flatten(io_lib:format("~p", [X])), + fun() -> check_roundtrip(X) end} + || X <- sources()]. + +sources() -> + [ "arith" + , "bool" + , "comp" + , "jumpif" + , "map" + , "memory" + , "remote" + , "test" + , "tuple" + ]. + +check_roundtrip(File) -> + AssemblerCode = read_file(File), + {Env, ByteCode} = assemble(AssemblerCode), + FateCode = disassemble(ByteCode), + DissasmCode = aeb_fate_asm:to_asm(FateCode), + io:format("~s~n", [AssemblerCode]), + io:format("~s~n", [DissasmCode]), + {Env2, ByteCode2} = assemble(DissasmCode), + Code1 = aeb_fate_asm:strip(ByteCode), + Code2 = aeb_fate_asm:strip(ByteCode2), + ?assertEqual(Code1, Code2). diff --git a/test/aeb_serialize_test.erl b/test/aeb_serialize_test.erl new file mode 100644 index 0000000..dcbe4e2 --- /dev/null +++ b/test/aeb_serialize_test.erl @@ -0,0 +1,82 @@ +%%%------------------------------------------------------------------- +%%% @copyright (C) 2018, Aeternity Anstalt +%%% @doc Basic tests for Fate serialization +%%% +%%% To run: +%%% TEST=aeb_serialize_test rebar3 eunit +%%% +%%% @end +%%%------------------------------------------------------------------- + +-module(aeb_serialize_test). + +-include_lib("eunit/include/eunit.hrl"). + +serialize_integer_test() -> + <<0>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(0)), + <<2>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(1)), + <<126>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(63)), + <<111, 0>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(64)), + <<111,130,255,255>> = aeb_fate_encoding:serialize(aeb_fate_data:make_integer(65535 + 64)), + <<111,184,129,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0>> = + aeb_fate_encoding:serialize(aeb_fate_data:make_integer(1 bsl 1024 + 64)). + +serialize_deserialize_test_() -> + [{lists:flatten(io_lib:format("~p", [X])), + fun() -> + ?assertEqual(X, + aeb_fate_encoding:deserialize(aeb_fate_encoding:serialize(X))) + end} + || X <- sources()]. + +make_int_list(N) -> [aeb_fate_data:make_integer(I) || I <- lists:seq(1, N)]. + +sources() -> + FortyTwo = aeb_fate_data:make_integer(42), + Unit = aeb_fate_data:make_unit(), + True = aeb_fate_data:make_boolean(true), + False = aeb_fate_data:make_boolean(false), + Nil = aeb_fate_data:make_list([]), + EmptyString = aeb_fate_data:make_string(""), + EmptyMap = aeb_fate_data:make_map(#{}), + [aeb_fate_data:make_integer(0), + aeb_fate_data:make_integer(1), + True, False, Unit, Nil, EmptyString, EmptyMap, + aeb_fate_data:make_list([True]), + aeb_fate_data:make_address( + <<0,1,2,3,4,5,6,7,8,9, + 0,1,2,3,4,5,6,7,8,9, + 0,1,2,3,4,5,6,7,8,9, + 1,2>>), + aeb_fate_data:make_string(<<"Hello">>), + aeb_fate_data:make_string( + <<"0123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789" + "0123456789012345678901234567890123456789">>), %% Magic concat 80 char string. + aeb_fate_data:make_tuple({True, FortyTwo}), + aeb_fate_data:make_tuple(list_to_tuple(make_int_list(65))), + aeb_fate_data:make_map(#{ aeb_fate_data:make_integer(1) => True, aeb_fate_data:make_integer(2) => False}), + aeb_fate_data:make_map(#{ aeb_fate_data:make_string(<<"foo">>) => aeb_fate_data:make_tuple({FortyTwo, True})}), + aeb_fate_data:make_list(make_int_list(3)), + aeb_fate_data:make_integer(-65), + aeb_fate_data:make_integer(65), + aeb_fate_data:make_integer(-32432847932847928374983), + aeb_fate_data:make_bits(0), + aeb_fate_data:make_bits(1), + aeb_fate_data:make_bits(-1), + aeb_fate_data:make_list(make_int_list(65)), + aeb_fate_data:make_variant(2, 0, {FortyTwo}), + aeb_fate_data:make_variant(2, 1, {}), + aeb_fate_data:make_list([aeb_fate_data:make_variant(3, 0, {})]), + aeb_fate_data:make_variant(255, 254, {}), + aeb_fate_data:make_variant(5, 3, {aeb_fate_data:make_boolean(true), + aeb_fate_data:make_list(make_int_list(3)), + aeb_fate_data:make_string(<<"foo">>)}) + + ]. diff --git a/test/asm_code/arith.fate b/test/asm_code/arith.fate new file mode 100644 index 0000000..ffd5311 --- /dev/null +++ b/test/asm_code/arith.fate @@ -0,0 +1,26 @@ +;; CONTRACT arith + +FUNCTION add (integer, integer) : integer + ADD a arg0 arg1 + RETURN + +FUNCTION sub (integer, integer) : integer + SUB a arg0 arg1 + RETURN + +FUNCTION mul (integer, integer) : integer + MUL a arg0 arg1 + RETURN + +FUNCTION div (integer, integer) : integer + DIV a arg0 arg1 + RETURN + +FUNCTION mod (integer, integer) : integer + MOD a arg0 arg1 + RETURN + +FUNCTION pow (integer, integer) : integer + POW a arg0 arg1 + RETURN + diff --git a/test/asm_code/bool.fate b/test/asm_code/bool.fate new file mode 100644 index 0000000..d12d881 --- /dev/null +++ b/test/asm_code/bool.fate @@ -0,0 +1,14 @@ +;; CONTRACT bool + +FUNCTION and(boolean, boolean) : boolean + AND a arg0 arg1 + RETURN + +FUNCTION or(boolean, boolean) : boolean + OR a arg0 arg1 + RETURN + +FUNCTION not(boolean) : boolean + NOT a arg0 + RETURN + diff --git a/test/asm_code/comp.fate b/test/asm_code/comp.fate new file mode 100644 index 0000000..b3f8345 --- /dev/null +++ b/test/asm_code/comp.fate @@ -0,0 +1,26 @@ +;; CONTRACT comp + +FUNCTION lt(integer, integer) : boolean + LT a arg0 arg1 + RETURN + +FUNCTION gt(integer, integer) : boolean + GT a arg0 arg1 + RETURN + +FUNCTION egt(integer, integer) : boolean + EGT a arg0 arg1 + RETURN + +FUNCTION elt(integer, integer) : boolean + ELT a arg0 arg1 + RETURN + +FUNCTION eq(integer, integer) : boolean + EQ a arg0 arg1 + RETURN + +FUNCTION neq(integer, integer) : boolean + NEQ a arg0 arg1 + RETURN + diff --git a/test/asm_code/identity.fate b/test/asm_code/identity.fate new file mode 100644 index 0000000..a2350dc --- /dev/null +++ b/test/asm_code/identity.fate @@ -0,0 +1,8 @@ +;; CONTRACT: Identity +FUNCTION id(integer) -> integer + RETURN + +;; Test the code from the shell +;; _build/default/rel/aessembler/bin/aessembler console + +;; aeb_aefa:file("../../../../test/asm_code/identity.fate", []). diff --git a/test/asm_code/jumpif.fate b/test/asm_code/jumpif.fate new file mode 100644 index 0000000..c7fefed --- /dev/null +++ b/test/asm_code/jumpif.fate @@ -0,0 +1,11 @@ +;; CONTRACT jumpif +FUNCTION skip(integer, integer) : integer +;; BB : 0 + PUSH arg1 + PUSH 0 + EQ a a arg0 + JUMPIF a 2 +;; BB : 1 + INCA + JUMP 2 + RETURN diff --git a/test/asm_code/map.fate b/test/asm_code/map.fate new file mode 100644 index 0000000..d841ea3 --- /dev/null +++ b/test/asm_code/map.fate @@ -0,0 +1,33 @@ +;; CONTRACT map +FUNCTION make_empty_map():{map, integer, boolean} + MAP_EMPTY a + RETURN + +FUNCTION map_update({map, integer, boolean}, integer, boolean):{map, integer, boolean} + MAP_UPDATE a arg0 arg1 arg2 + RETURN + +FUNCTION map_lookup({map, integer, boolean}, integer):boolean + MAP_LOOKUP a arg0 arg1 + RETURN + +FUNCTION map_lookup_default({map, integer, boolean}, integer): boolean + MAP_LOOKUPD a arg0 arg1 false + RETURN + +FUNCTION map_member({map, integer, boolean}, integer):boolean + MAP_MEMBER a arg0 arg1 + RETURN + +FUNCTION map_delete({map, integer, boolean}, integer):{map, integer, boolean} + MAP_DELETE a arg0 arg1 + RETURN + +FUNCTION map_member({map, integer, boolean}, integer) : boolean + MAP_MEMBER a arg0 arg1 + RETURN + +FUNCTION map_from_list({list, {tuple, [integer, boolean]}}) : {map, integer, boolean} + MAP_FROM_LIST a arg0 + RETURN + diff --git a/test/asm_code/memory.fate b/test/asm_code/memory.fate new file mode 100644 index 0000000..e162daa --- /dev/null +++ b/test/asm_code/memory.fate @@ -0,0 +1,31 @@ +;; CONTRACT memory +FUNCTION call(integer):integer + STORE var1 arg0 + PUSH 0 + CALL write + PUSH var1 + RETURN + +FUNCTION write(integer):integer + STORE var1 arg0 + RETURNR var1 + +FUNCTION dest_add(integer, integer): integer + STORE var1 arg0 + STORE var2 arg1 + ADD var3 var1 var2 + PUSH var3 + RETURN + +FUNCTION dest_add_imm(integer):integer + STORE var1 arg0 + ADD var3 var1 2 + PUSH var3 + RETURN + +FUNCTION dest_add_stack(integer, integer): integer + STORE var1 arg0 + PUSH arg1 + ADD var3 var1 a + PUSH var3 + RETURN diff --git a/test/asm_code/remote.fate b/test/asm_code/remote.fate new file mode 100644 index 0000000..5ad09ea --- /dev/null +++ b/test/asm_code/remote.fate @@ -0,0 +1,4 @@ +;; CONTRACT remote +FUNCTION add_five(integer):integer + ADD a 5 arg0 + RETURN diff --git a/test/asm_code/test.fate b/test/asm_code/test.fate new file mode 100644 index 0000000..aae9102 --- /dev/null +++ b/test/asm_code/test.fate @@ -0,0 +1,45 @@ +;; CONTRACT: Test +FUNCTION id(integer) -> integer + RETURN + +FUNCTION jumps() -> integer + PUSH 0 + JUMP 3 + NOP + JUMP 2 + NOP + RETURN + NOP + JUMP 1 + +FUNCTION inc(integer) -> integer + INCA + INCA + RETURN + +FUNCTION call(integer) -> integer + INCA + CALL inc + INCA + RETURN + + +FUNCTION tailcall(integer) -> integer + INCA + CALL_T inc + +FUNCTION remote_call(integer) : integer + PUSH arg0 + CALL_R remote.add_five + INCA + RETURN + +FUNCTION remote_tailcall(integer) : integer + PUSH arg0 + CALL_TR remote add_five + +;; Test the code from the shell +;; _build/default/rel/aessembler/bin/aessembler console + +;; aeb_aefa:file("../../../../test/asm_code/test.fate", []). +;; f(Asm), f(Env), f(BC), Asm = aefa_asm:read_file("../../../../test/asm_code/test.fate"), {Env, BC} = aefa_asm:asm_to_bytecode(Asm, []), aefa_asm:bytecode_to_fate_code(BC, []). \ No newline at end of file diff --git a/test/asm_code/tuple.fate b/test/asm_code/tuple.fate new file mode 100644 index 0000000..6240dca --- /dev/null +++ b/test/asm_code/tuple.fate @@ -0,0 +1,35 @@ +FUNCTION make_0tuple():{tuple, []} +;; BB : 0 + TUPLE 0 + RETURN + +FUNCTION make_2tuple(integer, integer):{tuple, [integer, integer]} +;; BB : 0 + PUSH arg0 + PUSH arg1 + TUPLE 2 + RETURN + +FUNCTION make_5tuple(integer, integer, integer, integer, integer): + {tuple, [integer, integer, integer, integer, integer]} +;; BB : 0 + PUSH arg0 + PUSH arg1 + PUSH arg2 + PUSH arg3 + PUSH arg4 + TUPLE 5 + RETURN + +FUNCTION element1(integer, integer): integer +;; BB : 0 + PUSH arg0 + PUSH arg1 + TUPLE 2 + ELEMENT integer a 1 a + RETURN + +FUNCTION element({tuple, [integer, integer]}, integer): integer +;; BB : 0 + ELEMENT integer a arg1 arg0 + RETURN