Compare commits

...

34 Commits

Author SHA1 Message Date
Thomas Arts 9d056dc620 Add eqc profile 2019-02-28 13:34:03 +01:00
Thomas Arts a51a864059 Formatting differently 2019-02-28 13:33:47 +01:00
Thomas Arts 899bff9111 unit is printed () 2019-02-28 13:33:26 +01:00
Thomas Arts b5daedaf95 Since Tag < Size, Size cannot be zero 2019-02-28 10:38:33 +01:00
Erik Stenman 8dd8e89c1e Rename NUMBER op to GENERATION and add MICROBLOCK instruction. 2019-02-28 08:13:26 +01:00
Erik Stenman dd1a6a9c3d Generate tokens in scanner from definitions. 2019-02-28 07:55:51 +01:00
Erik Stenman 161b5a6106 Renumber opcodes. Add primops. 2019-02-27 17:47:08 +01:00
Erik Stenman 268208ec98 Use test target for ci. 2019-02-27 15:36:48 +01:00
Erik Stenman 9411a131fc Spell eunit the right way. 2019-02-27 15:34:06 +01:00
Erik Stenman c624f4956c Test targets and cleanup. 2019-02-27 15:34:06 +01:00
Erik Stenman ab150ce7f8 Generate the code from the makefile. Remove generated files. 2019-02-27 15:34:06 +01:00
Erik Stenman c85af9e7f3 Generate code for fate ops from spec. 2019-02-27 15:34:06 +01:00
Erik Stenman 01ae99f7e8 Removed unused enacl lib. Use eblake2 for hash. (#8)
* Removed unused lib.

* Replace local blake2 implementation with eblake2.

* Add eblake2 dep to app file.

* Add eblake2 to rebar config.

* Use hex for eblake2.

* Bump version.

* Replace local rlp with aeserialization repo. Use ref till first release is available.

* Remove unused vars.
2019-02-26 08:53:46 +01:00
Erik Stenman a35307f61b Add annotations (comments) to bytecode. Add strip function to remove symboltable and annotations from bytecode. 2019-02-25 07:57:08 +01:00
Erik Stenman d04a827f05 Add fate code pretty printer. Add symbol table to binary. Add tests of rundtrip serialization and deserialization. 2019-02-23 22:13:19 +01:00
Erik Stenman 7e26912bf9 Always escriptize. 2019-02-20 19:16:56 +01:00
Erik Stenman 8ba5f9e2b1 Provide binary profile. 2019-02-20 19:04:43 +01:00
Erik Stenman 3c056db0b5 Cleanup. 2019-02-20 14:06:11 +01:00
Erik Stenman 5e9d34849f Explicit export. 2019-02-20 11:37:43 +01:00
Erik Stenman c0f2ac3163 Instructions are uppercase. 2019-02-20 09:49:11 +01:00
Erik Stenman dae2dbeed6 Add DUPA 2019-02-19 16:11:53 +01:00
Erik Stenman ce33ba8818 Add new instructions. 2019-02-19 12:15:01 +01:00
Erik Stenman 3ddae5e674 Code generation api. 2019-02-19 11:50:23 +01:00
Erik Stenman 7b671d2187 Lexer for inc and switch. 2019-02-18 18:13:40 +01:00
Erik Stenman 16644ded72 Handle most ops. 2019-02-18 18:09:00 +01:00
Erik Stenman 36f910aff4 Add SWITCH opcodes. 2019-02-15 21:44:36 +01:00
Erik Stenman 08e169c3b2 New format for functions, signatures and code. 2019-02-15 16:14:20 +01:00
Erik Stenman 18eb37a8c5 Fix function_call. 2019-02-15 15:38:34 +01:00
Erik Stenman b95827b2d0 Parse call. 2019-02-15 14:34:32 +01:00
Erik Stenman afdb78b933 Fix erros found by dialyzer and warnings. 2019-02-15 13:47:40 +01:00
Erik Stenman c5a9878bd9 Rename to library standard. 2019-02-15 12:34:46 +01:00
Erik Stenman e6623bd252 Merge branch 'fortuna' of github.com:aeternity/aebytecode into fortuna 2019-02-15 11:33:32 +01:00
Erik Stenman 37f97e3837 Bump version. 2019-02-15 11:33:13 +01:00
Erik Stenman a539378405 Pt 162805963 fate opcodes (#6)
* First iteration of assembler.
* Stand alone assembler.
2019-02-15 11:24:25 +01:00
26 changed files with 2462 additions and 10 deletions
+4 -4
View File
@@ -19,16 +19,16 @@ jobs:
- dialyzer-cache-v1-
- run:
name: Build
command: rebar3 compile
command: make
- run:
name: Static Analysis
command: rebar3 dialyzer
command: make dialyzer
- run:
name: Eunit
command: rebar3 eunit
command: make eunit
- run:
name: Common Tests
command: rebar3 ct
command: make test
- save_cache:
key: dialyzer-cache-v1-{{ .Branch }}-{{ .Revision }}
paths:
+6
View File
@@ -9,4 +9,10 @@ rel/example_project
.concrete/DEV_MODE
.rebar
aeb_asm_scan.erl
aeb_fate_asm_scan.erl
aeb_fate_asm_scan.xrl
_build/
aefateasm
include/aeb_fate_opcodes.hrl
src/aeb_fate_code.erl
src/aeb_fate_opcodes.erl
+41
View File
@@ -0,0 +1,41 @@
REBAR ?= rebar3
all: local
local: src/aeb_fate_opcodes.erl src/aeb_fate_code.erl include/aeb_fate_opcodes.hrl src/aeb_fate_asm_scan.xrl
@$(REBAR) as local release
console: local
@$(REBAR) as local shell
clean:
@$(REBAR) clean
rm -f src/aeb_fate_opcodes.erl
rm -f src/aeb_fate_code.erl
rm -f include/aeb_fate_opcodes.hrl
dialyzer: local
@$(REBAR) as local dialyzer
distclean: clean
@rm -rf _build/
eunit: local
@$(REBAR) as local eunit
test: local
@$(REBAR) as local eunit
ebin/aeb_fate_generate_ops.beam: src/aeb_fate_generate_ops.erl ebin
erlc -o $(dir $@) $<
src/aeb_fate_opcodes.erl src/aeb_fate_code.erl include/aeb_fate_opcodes.hrl src/aeb_fate_asm_scan.xrl: ebin/aeb_fate_generate_ops.beam
erl -pa ebin/ -noshell -s aeb_fate_generate_ops gen_and_halt src/ include/
ebin:
mkdir ebin
+55
View File
@@ -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).
+43 -2
View File
@@ -2,10 +2,51 @@
{erl_opts, [debug_info]}.
{deps, []}.
{deps, [ {eblake2, "1.0.0"}
, {aeserialization, {git, "https://github.com/aeternity/aeserialization.git",
{ref, "b55c372"}}}
, {getopt, "1.0.1"}
]}.
{escript_incl_apps, [aebytecode, eblake2, aeserialization, 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, {aebytecode, "2.0.1"},
[aebytecode, eblake2, getopt]},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}]}.
{profiles, [{binary, [
{deps, [ {eblake2, "1.0.0"}
, {aeserialization, {git, "https://github.com/aeternity/aeserialization.git",
{ref, "b55c372"}}}
, {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
]}
]},
{eqc, [{erl_opts, [{parse_transform, eqc_cover}]},
{extra_src_dirs, ["quickcheck"]} %% May not be called eqc!
]}
]}.
+16 -1
View File
@@ -1 +1,16 @@
[].
{"1.1.0",
[{<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git",
{ref,"b55c3726f4a21063721c68d6fa7fda39121edf11"}},
0},
{<<"base58">>,
{git,"https://github.com/aeternity/erl-base58.git",
{ref,"60a335668a60328a29f9731b67c4a0e9e3d50ab6"}},
1},
{<<"eblake2">>,{pkg,<<"eblake2">>,<<"1.0.0">>},0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0}]}.
[
{pkg_hash,[
{<<"eblake2">>, <<"EC8AD20E438AAB3F2E8D5D118C366A0754219195F8A0F536587440F8F9BCF2EF">>},
{<<"getopt">>, <<"C73A9FA687B217F2FF79F68A3B637711BB1936E712B521D8CE466B29CBF7808A">>}]}
].
+887
View File
@@ -0,0 +1,887 @@
%%%-------------------------------------------------------------------
%%% @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 followed by an integer
%%% arg0
%%% References to variables/registers start with var followed by an integer
%%% var0
%%% References to stack postions is either a (for stack 0)
%%% or start with stack followed by an integer
%%% stack1
%%% a
%%%
%%% 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 = << (aeser_rlp:encode(list_to_binary(ByteList)))/binary,
(aeser_rlp:encode(list_to_binary(Signatures)))/binary,
(aeser_rlp:encode(SymbolTable))/binary,
(aeser_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} = aeser_rlp:decode_one(ByteCode),
Code.
bytecode_to_fate_code(Bytes, _Options) ->
{ByteCode, Rest1} = aeser_rlp:decode_one(Bytes),
{Signatures, Rest2} = aeser_rlp:decode_one(Rest1),
{SymbolTable, Rest3} = aeser_rlp:decode_one(Rest2),
{Annotations, <<>>} = aeser_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:8, A, B, C, D, Rest/binary>>,
#{ function := none
, bb := 0
, current_bb_code := []
} = Env) ->
{Sig, Rest2} = deserialize_signature(Rest),
Env2 = Env#{function => {<<A,B,C,D>>, Sig}},
deserialize(Rest2, Env2);
deserialize(<<?FUNCTION:8, A, B, C, D, Rest/binary>>,
#{ 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 => {<<A,B,C,D>>, NewSig}
, code => #{}
, functions => Funs#{F => {Sig, Program}}},
deserialize(Rest2, Env2);
_ ->
Env2 = Env#{ bb => 0
, current_bb_code => []
, function => {<<A,B,C,D>>, NewSig}
, code => #{}
, functions =>
Funs#{F => {Sig,
Program#{ BB => lists:reverse(Code)}}}},
deserialize(Rest2, Env2)
end;
deserialize(<<Op:8, Rest/binary>>,
#{ 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),
<<ArgType:8, Rest3/binary>> = 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) ->
<<ArgType:8, Rest2/binary>> = 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 ->
<<ArgType:8, Rest2/binary>> = Rest,
{Arg, Rest3} = aeb_fate_encoding:deserialize_one(Rest2),
Modifier = bits_to_modifier(ArgType),
{Rest3, [{OpName, {Modifier, Arg}} | Code]};
2 ->
<<ArgType:8, Rest2/binary>> = 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 ->
<<ArgType:8, Rest2/binary>> = 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 ->
<<ArgType:8, Rest2/binary>> = 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.
-spec serialize_type(aeb_fate_data:fate_type_type()) -> [byte()].
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, <<A:8, B:8, C:8, D:8,_/binary>> } = eblake2:blake2b(?HASH_BYTES, list_to_binary(Id)),
<<A,B,C,D>>.
%% 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).
+99
View File
@@ -0,0 +1,99 @@
%%% -*- erlang-indent-level:4; indent-tabs-mode: nil -*-
%%%-------------------------------------------------------------------
%%% @copyright (C) 2019, aeternity Anstalt
%%% @doc
%%% Handling FATE code.
%%% @end
###REPLACEWITHNOTE###
%%%-------------------------------------------------------------------
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}}.
###REPLACEWITHOPTOKENS###
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),
<<N:256>>.
scan(S) ->
string(S).
drop_prefix(C, [C|Rest]) ->
drop_prefix(C, Rest);
drop_prefix(_, Tail) -> Tail.
+194
View File
@@ -0,0 +1,194 @@
%% 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_tuple() :: ?FATE_TUPLE_T.
-type fate_type_type() :: integer
| boolean
| {list, fate_type()}
| {map, fate_type(), fate_type()}
| {tuple, [fate_type()]}
| address
| bits
| {variant, integer()}.
-type fate_type() ::
fate_boolean()
| fate_integer()
| fate_nil()
| fate_list()
| fate_unit()
| fate_tuple()
| fate_string()
| fate_address()
| fate_variant()
| fate_map()
| fate_type_type().
-export_type([fate_type/0
, fate_boolean/0
, fate_integer/0
, fate_nil/0
, fate_list/0
, fate_unit/0
, fate_tuple/0
, fate_string/0
, fate_address/0
, fate_variant/0
, fate_map/0
, fate_type_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 = <<I:256>>, 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:256>>)) -> {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_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) -> "()";
format(?FATE_TUPLE(T)) ->
["( ", lists:join(", ", [ format(E) || E <- erlang:tuple_to_list(T)]), " )"];
format(S) when ?IS_FATE_STRING(S) -> [S];
format(?FATE_VARIANT(Size, Tag, T)) ->
["(| ",
lists:join("| ", [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(List) ->
["[ ", lists:join(", ", [format(E) || E <- List]), " ]"].
format_kvs(List) ->
lists:join(", ", [ [format(K), " => ", format(V)] || {K, V} <- List]).
%% -- 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 = <<I:256>>,
Bin.
address_to_base58(<<A:256>>) ->
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]).
+260
View File
@@ -0,0 +1,260 @@
%% 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) -> <<?TRUE>>;
serialize(?FATE_FALSE) -> <<?FALSE>>;
serialize(?FATE_NIL) -> <<?NIL>>; %% ! Untyped
serialize(?FATE_UNIT) -> <<?EMPTY_TUPLE>>; %% ! Untyped
serialize(M) when ?IS_FATE_MAP(M), ?FATE_MAP_SIZE(M) =:= 0 -> <<?EMPTY_MAP>>; %% ! Untyped
serialize(?FATE_EMPTY_STRING) -> <<?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),
<<Size:6, ?SHORT_STRING:2, Bytes/binary>>;
serialize(String) when ?IS_FATE_STRING(String),
?FATE_STRING_SIZE(String) > 0,
?FATE_STRING_SIZE(String) >= ?SHORT_STRING_SIZE ->
Bytes = ?FATE_STRING_VALUE(String),
<<?LONG_STRING, (aeser_rlp:encode(Bytes))/binary>>;
serialize(?FATE_ADDRESS(Address)) when is_binary(Address) ->
<<?ADDRESS, (aeser_rlp:encode(Address))/binary>>;
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 ->
<<S:4, ?SHORT_TUPLE:4, Rest/binary>>;
true ->
Size = rlp_integer(S - ?SHORT_TUPLE_SIZE),
<<?LONG_TUPLE:8, Size/binary, Rest/binary>>
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 ->
<<S:4, ?SHORT_LIST:4, Rest/binary>>;
true ->
Val = rlp_integer(S - ?SHORT_LIST_SIZE),
<<?LONG_LIST, Val/binary, Rest/binary>>
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 >>,
<<?MAP,
(rlp_integer(Size))/binary,
(Elements)/binary>>;
serialize(?FATE_VARIANT(Size, Tag, Values)) when 0 < Size, Size < 256,
0 =< Tag, Tag < Size ->
<<?VARIANT, Size:8, Tag:8,
(serialize(?FATE_TUPLE(Values)))/binary
>>.
%% -----------------------------------------------------
rlp_integer(S) when S >= 0 ->
aeser_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:1, Abs:6, ?SMALL_INT:1>>;
Sign =:= ?NEG_SIGN -> <<?NEG_BIG_INT,
(rlp_integer(Abs - ?SMALL_INT_SIZE))/binary>>;
Sign =:= ?POS_SIGN -> <<?POS_BIG_INT,
(rlp_integer(Abs - ?SMALL_INT_SIZE))/binary>>
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 -> <<?NEG_BITS, (rlp_integer(Abs))/binary>>;
Sign =:= ?POS_SIGN -> <<?POS_BITS, (rlp_integer(Abs))/binary>>
end.
-spec deserialize(binary()) -> aeb_fate_data:fate_type().
deserialize(B) ->
{T, <<>>} = deserialize2(B),
T.
deserialize_one(B) -> deserialize2(B).
deserialize2(<<?POS_SIGN:1, I:6, ?SMALL_INT:1, Rest/binary>>) ->
{?MAKE_FATE_INTEGER(I), Rest};
deserialize2(<<?NEG_SIGN:1, I:6, ?SMALL_INT:1, Rest/binary>>) ->
{?MAKE_FATE_INTEGER(-I), Rest};
deserialize2(<<?NEG_BIG_INT, Rest/binary>>) ->
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
{?MAKE_FATE_INTEGER(-binary:decode_unsigned(Bint) - ?SMALL_INT_SIZE),
Rest2};
deserialize2(<<?POS_BIG_INT, Rest/binary>>) ->
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
{?MAKE_FATE_INTEGER(binary:decode_unsigned(Bint) + ?SMALL_INT_SIZE),
Rest2};
deserialize2(<<?NEG_BITS, Rest/binary>>) ->
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
{?FATE_BITS(-binary:decode_unsigned(Bint)), Rest2};
deserialize2(<<?POS_BITS, Rest/binary>>) ->
{Bint, Rest2} = aeser_rlp:decode_one(Rest),
{?FATE_BITS(binary:decode_unsigned(Bint)), Rest2};
deserialize2(<<?LONG_STRING, Rest/binary>>) ->
{String, Rest2} = aeser_rlp:decode_one(Rest),
{?MAKE_FATE_STRING(String), Rest2};
deserialize2(<<S:6, ?SHORT_STRING:2, Rest/binary>>) ->
String = binary:part(Rest, 0, S),
Rest2 = binary:part(Rest, byte_size(Rest), - (byte_size(Rest) - S)),
{?MAKE_FATE_STRING(String), Rest2};
deserialize2(<<?ADDRESS, Rest/binary>>) ->
{A, Rest2} = aeser_rlp:decode_one(Rest),
{?FATE_ADDRESS(A), Rest2};
deserialize2(<<?TRUE, Rest/binary>>) ->
{?FATE_TRUE, Rest};
deserialize2(<<?FALSE, Rest/binary>>) ->
{?FATE_FALSE, Rest};
deserialize2(<<?NIL, Rest/binary>>) ->
{?FATE_NIL, Rest};
deserialize2(<<?EMPTY_TUPLE, Rest/binary>>) ->
{?FATE_UNIT, Rest};
deserialize2(<<?EMPTY_MAP, Rest/binary>>) ->
{?MAKE_FATE_MAP(#{}), Rest};
deserialize2(<<?EMPTY_STRING, Rest/binary>>) ->
{?FATE_EMPTY_STRING, Rest};
deserialize2(<<?LONG_TUPLE, Rest/binary>>) ->
{BSize, Rest1} = aeser_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(<<S:4, ?SHORT_TUPLE:4, Rest/binary>>) ->
{List, Rest1} = deserialize_elements(S, Rest),
{?FATE_TUPLE(list_to_tuple(List)), Rest1};
deserialize2(<<?LONG_LIST, Rest/binary>>) ->
{BLength, Rest1} = aeser_rlp:decode_one(Rest),
Length = binary:decode_unsigned(BLength) + ?SHORT_LIST_SIZE,
{List, Rest2} = deserialize_elements(Length, Rest1),
{?MAKE_FATE_LIST(List), Rest2};
deserialize2(<<S:4, ?SHORT_LIST:4, Rest/binary>>) ->
{List, Rest1} = deserialize_elements(S, Rest),
{?MAKE_FATE_LIST(List), Rest1};
deserialize2(<<?MAP, Rest/binary>>) ->
{BSize, Rest1} = aeser_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(<<?VARIANT, Size:8, Tag:8, Rest/binary>>) ->
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}.
+402
View File
@@ -0,0 +1,402 @@
-module(aeb_fate_generate_ops).
-export([ gen_and_halt/1
, generate/0]).
gen_and_halt([SrcDirArg, IncludeDirArg]) ->
generate(atom_to_list(SrcDirArg),
atom_to_list(IncludeDirArg)),
halt().
generate() ->
generate("src/", "include/").
generate(Src, Include) ->
Ops = gen(ops_defs()),
%% io:format("ops: ~p\n", [Ops]),
HrlFile = Include ++ "aeb_fate_opcodes.hrl",
generate_header_file(HrlFile, Ops),
generate_opcodes_ops(aeb_fate_opcodes, HrlFile, Src, Ops),
generate_code_ops(aeb_fate_code, Src, Ops),
generate_scanner("aeb_fate_asm_scan.template", "aeb_fate_asm_scan.xrl", Src, Ops).
%% TODO: Some real gas numbers...
ops_defs() ->
%% Opname, Opcode, args, end_bb, gas, format, Constructor, Documentation
[ { 'RETURN', 16#00, 0, true, 2, atomic, return, "Return from function call pop stack to arg0. The type of the retun value has to match the return type of the function."}
, { 'RETURNR', 16#01, 1, true, 2, [a], returnr, "Return from function call copy Arg0 to arg0. The type of the retun value has to match the return type of the function."}
, { 'CALL', 16#02, 1, true, 4, [is], call, "Call given function with args on stack. The types of the arguments has to match the argument typs of the function."}
, { 'CALL_R', 16#03, 2, true, 8, [a,is], call_r, "Remote call to given contract and function. The types of the arguments has to match the argument typs of the function."}
, { 'CALL_T', 16#04, 1, true, 4, [is], call_t, "Tail call to given function. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
, { 'CALL_TR', 16#05, 2, true, 8, [a,is], call_tr, "Remote tail call to given contract and function. The types of the arguments has to match the argument typs of the function. And the return type of the called function has to match the type of the current function."}
, { 'JUMP', 16#06, 1, true, 3, [ii], jump, "Jump to a basic block. The basic block has to exist in the current function."}
, { 'JUMPIF', 16#07, 2, true, 4, [a,ii], jumpif, "Conditional jump to a basic block. If Arg0 then jump to Arg1."}
, { 'SWITCH_V2', 16#08, 3, true, 4, [a,ii,ii], switch, "Conditional jump to a basic block on variant tag."}
, { 'SWITCH_V3', 16#09, 4, true, 4, [a,ii,ii,ii], switch, "Conditional jump to a basic block on variant tag."}
, { 'SWITCH_VN', 16#0a, 2, true, 4, [a,li], switch, "Conditional jump to a basic block on variant tag."}
, { 'PUSH', 16#0b, 1, false, 2, [a], push, "Push argument to stack."}
, { 'DUPA', 16#0c, 0, false, 3, atomic, dup, "push copy of accumulator on stack."}
, { 'DUP', 16#0d, 1, false, 3, [a], dup, "push Arg0 stack pos on top of stack."}
, { 'POP', 16#0e, 1, false, 3, [a], pop, "Arg0 := top of stack."}
, { 'STORE', 16#0f, 2, false, 3, [a,a], store, "Arg0 := Arg1."}
, { 'INCA', 16#10, 0, false, 2, atomic, inc, "Increment accumulator."}
, { 'INC', 16#11, 1, false, 2, [a], inc, "Increment argument."}
, { 'DECA', 16#12, 0, false, 2, atomic, dec, "Decrement accumulator."}
, { 'DEC', 16#13, 1, false, 2, [a], dec, "Decrement argument."}
, { 'ADD', 16#14, 3, false, 3, [a,a,a], add, "Arg0 := Arg1 + Arg2."}
, { 'SUB', 16#15, 3, false, 3, [a,a,a], sub, "Arg0 := Arg1 - Arg2."}
, { 'MUL', 16#16, 3, false, 3, [a,a,a], mul, "Arg0 := Arg1 * Arg2."}
, { 'DIV', 16#17, 3, false, 3, [a,a,a], divide, "Arg0 := Arg1 / Arg2."}
, { 'MOD', 16#18, 3, false, 3, [a,a,a], modulo, "Arg0 := Arg1 mod Arg2."}
, { 'POW', 16#19, 3, false, 3, [a,a,a], pow, "Arg0 := Arg1 ^ Arg2."}
, { 'LT', 16#20, 3, false, 3, [a,a,a], lt, "Arg0 := Arg1 < Arg2."}
, { 'GT', 16#21, 3, false, 3, [a,a,a], gt, "Arg0 := Arg1 > Arg2."}
, { 'EQ', 16#22, 3, false, 3, [a,a,a], eq, "Arg0 := Arg1 = Arg2."}
, { 'ELT', 16#23, 3, false, 3, [a,a,a], elt, "Arg0 := Arg1 =< Arg2."}
, { 'EGT', 16#24, 3, false, 3, [a,a,a], egt, "Arg0 := Arg1 >= Arg2."}
, { 'NEQ', 16#25, 3, false, 3, [a,a,a], neq, "Arg0 := Arg1 /= Arg2."}
, { 'AND', 16#26, 3, false, 3, [a,a,a], and_op, "Arg0 := Arg1 and Arg2."}
, { 'OR', 16#27, 3, false, 3, [a,a,a], or_op, "Arg0 := Arg1 or Arg2."}
, { 'NOT', 16#28, 2, false, 3, [a,a], not_op, "Arg0 := not Arg1."}
, { 'TUPLE', 16#29, 1, false, 3, [ii], tuple, "Create a tuple of size = Arg0. Elements on stack."}
, { 'ELEMENT', 16#2a, 4, false, 3, [t,a,a,a], element_op, "Arg1 := element(Arg2, Arg3). The element should be of type Arg1"}
, { 'MAP_EMPTY', 16#2b, 1, false, 3, [a], map_empty, "Arg0 := #{}."}
, { 'MAP_LOOKUP', 16#2c, 3, false, 3, [a,a,a], map_lookup, "Arg0 := lookup key Arg2 in map Arg1."}
, { 'MAP_LOOKUPD', 16#2d, 4, false, 3, [a,a,a,a], map_lookup, "Arg0 := lookup key Arg2 in map Arg1 if key exists in map otherwise Arg0 := Arg3."}
, { 'MAP_UPDATE', 16#2e, 4, false, 3, [a,a,a,a], map_update, "Arg0 := update key Arg2 in map Arg1 with value Arg3."}
, { 'MAP_DELETE', 16#2f, 3, false, 3, [a,a,a], map_delete, "Arg0 := delete key Arg2 from map Arg1."}
, { 'MAP_MEMBER', 16#30, 3, false, 3, [a,a,a], map_member, "Arg0 := true if key Arg2 is in map Arg1."}
, { 'MAP_FROM_LIST',16#31, 2, false, 3, [a,a], map_from_list, "Arg0 := make a map from (key, value) list in Arg1."}
, { 'NIL', 16#32, 1, false, 3, [a], nil, "Arg0 := []."}
, { 'IS_NIL', 16#33, 2, false, 3, [a,a], is_nil, "Arg0 := true if Arg1 == []."}
, { 'CONS', 16#34, 3, false, 3, [a,a,a], cons, "Arg0 := [Arg1|Arg2]."}
, { 'HD', 16#35, 2, false, 3, [a,a], hd, "Arg0 := head of list Arg1."}
, { 'TL', 16#36, 2, false, 3, [a,a], tl, "Arg0 := tail of list Arg1."}
, { 'LENGTH', 16#37, 2, false, 3, [a,a], length, "Arg0 := length of list Arg1."}
, { 'STR_EQ', 16#38, 3, false, 3, [a,a,a], str_eq, "Arg0 := true iff the strings Arg1 and Arg2 are the same."}
, { 'STR_JOIN', 16#39, 3, false, 3, [a,a,a], str_join, "Arg0 := string Arg1 followed by string Arg2."}
, { 'INT_TO_STR', 16#40, 2, false, 3, [a,a], int_to_str, "Arg0 := turn integer Arg1 into a string."}
, { 'ADDR_TO_STR', 16#41, 2, false, 3, [a,a], addr_to_str, "Arg0 := turn address Arg1 into a string."}
, { 'STR_REVERSE', 16#42, 2, false, 3, [a,a], str_reverse, "Arg0 := the reverse of string Arg1."}
, { 'INT_TO_ADDR', 16#43, 2, false, 3, [a,a], int_to_addr, "Arg0 := turn integer Arg1 into an address."}
, { 'VARIANT', 16#44, 4, false, 3, [a,a,a,a], variant, "Arg0 := create a variant of size Arg1 with the tag Arg2 (Arg2 < Arg1) and take Arg3 elements from the stack."}
, { 'VARIANT_TEST', 16#45, 3, false, 3, [a,a,a], variant_test, "Arg0 := true if variant Arg1 has the tag Arg2."}
, { 'VARIANT_ELEMENT',16#46, 3, false, 3, [a,a,a], variant_element, "Arg0 := element number Arg2 from variant Arg1."}
, { 'BITS_NONEA', 16#47, 0, false, 3, atomic, bits_none, "accumulator := empty bitmap."}
, { 'BITS_NONE', 16#48, 1, false, 3, [a], bits_none, "Arg0 := empty bitmap."}
, { 'BITS_ALLA', 16#49, 0, false, 3, atomic, bits_all, "accumulator := full bitmap."}
, { 'BITS_ALL', 16#50, 1, false, 3, [a], bits_all, "Arg0 := full bitmap."}
, { 'BITS_ALL_N', 16#51, 2, false, 3, [a,a], bits_all_n, "Arg0 := bitmap with Arg1 bits set."}
, { 'BITS_SET', 16#52, 3, false, 3, [a,a,a], bits_set, "Arg0 := set bit Arg2 of bitmap Arg1."}
, { 'BITS_CLEAR', 16#53, 3, false, 3, [a,a,a], bits_clear, "Arg0 := clear bit Arg2 of bitmap Arg1."}
, { 'BITS_TEST', 16#54, 3, false, 3, [a,a,a], bits_test, "Arg0 := true if bit Arg2 of bitmap Arg1 is set."}
, { 'BITS_SUM', 16#55, 2, false, 3, [a,a], bits_sum, "Arg0 := sum of set bits in bitmap Arg1. Exception if infinit bitmap."}
, { 'BITS_OR', 16#56, 3, false, 3, [a,a,a], bits_or, "Arg0 := Arg1 v Arg2."}
, { 'BITS_AND', 16#57, 3, false, 3, [a,a,a], bits_and, "Arg0 := Arg1 ^ Arg2."}
, { 'BITS_DIFF', 16#58, 3, false, 3, [a,a,a], bits_diff, "Arg0 := Arg1 - Arg2."}
, { 'ADDRESS', 16#59, 1, false, 3, [a], address, "Arg0 := The current contract address."}
, { 'BALANCE', 16#5a, 1, false, 3, [a], balance, "Arg0 := The current contract address."}
, { 'ORIGIN', 16#5b, 1, false, 3, [a], origin, "Arg0 := Address of contract called by the call transaction."}
, { 'CALLER', 16#5c, 1, false, 3, [a], caller, "Arg0 := The address that signed the call transaction."}
, { 'GASPRICE', 16#5d, 1, false, 3, [a], gasprice, "Arg0 := The current gas price."}
, { 'BLOCKHASH', 16#5e, 1, false, 3, [a], blockhash, "Arg0 := The current blockhash."} %% TODO: Do we support has at height?
, { 'BENEFICIARY', 16#5f, 1, false, 3, [a], beneficiary, "Arg0 := The address of the current beneficiary."}
, { 'TIMESTAMP', 16#60, 1, false, 3, [a], timestamp, "Arg0 := The current timestamp. Unrelaiable, don't use for anything."}
, { 'GENERATION', 16#61, 1, false, 3, [a], generation, "Arg0 := The block height of the cureent generation."}
, { 'MICROBLOCK', 16#62, 1, false, 3, [a], microblock, "Arg0 := The current micro block number."}
, { 'DIFFICULTY', 16#63, 1, false, 3, [a], difficulty, "Arg0 := The current difficulty."}
, { 'GASLIMIT', 16#64, 1, false, 3, [a], gaslimit, "Arg0 := The current gaslimit."}
, { 'GAS', 16#65, 1, false, 3, [a], gas, "Arg0 := The amount of gas left."}
, { 'LOG0', 16#66, 2, false, 3, [a,a], log, "Create a log message in the call object."}
, { 'LOG1', 16#67, 3, false, 3, [a,a,a], log, "Create a log message with one topic in the call object."}
, { 'LOG2', 16#68, 4, false, 3, [a,a,a,a], log, "Create a log message with two topics in the call object."}
, { 'LOG3', 16#69, 5, false, 3, [a,a,a,a,a], log, "Create a log message with three topics in the call object."}
, { 'LOG4', 16#6a, 6, false, 3, [a,a,a,a,a,a], log, "Create a log message with four topics in the call object."}
, { 'DEACTIVATE', 16#6b, 0, false, 3, atomic, deactivate, "Mark the current contract for deactication."}
%% Transaction ops
, { 'SPEND', 16#6c, 2, false,3, [a,a], spend, "Transfer Arg0 tokens to account Arg1. (If the contract account has at least that many tokens."}
, { 'ORACLE_REGISTER', 16#6d, 6, false,3, [a,a,a,a,a,a], oracle_register, "Mark the current contract for deactication."}
%% TODO:
, { 'ORACLE_QUERY', 16#6e, 0, false,3, atomic, oracle_query, ""}
, { 'ORACLE_RESPOND', 16#6f, 0, false,3, atomic, oracle_respond, ""}
, { 'ORACLE_EXTEND', 16#70, 0, false,3, atomic, oracle_extend, ""}
, { 'ORACLE_GET_ANSWER', 16#71, 0, false,3, atomic, oracle_get_answer, ""}
, { 'ORACLE_GET_QUESTION', 16#72, 0, false,3, atomic,oracle_get_question, ""}
, { 'ORACLE_QUERY_FEE', 16#73, 0, false,3, atomic, oracle_query_fee, ""}
, { 'AENS_RESOLVE', 16#74, 0, false,3, atomic, aens_resolve, ""}
, { 'AENS_PRECLAIM', 16#75, 0, false,3, atomic, aens_preclaim, ""}
, { 'AENS_CLAIM', 16#76, 0, false,3, atomic, aens_claim, ""}
, { 'AENS_UPDATE', 16#77, 0, false,3, atomic, aend_update, ""}
, { 'AENS_TRANSFER', 16#78, 0, false,3, atomic, aens_transfer, ""}
, { 'AENS_REVOKE', 16#79, 0, false,3, atomic, aens_revoke, ""}
, { 'ECVERIFY', 16#7a, 0, false,3, atomic, ecverify, ""}
, { 'SHA3', 16#7b, 0, false,3, atomic, sha3, ""}
, { 'SHA256', 16#7c, 0, false,3, atomic, sha256, ""}
, { 'BLAKE2B', 16#7d, 0, false,3, atomic, blake2b, ""}
, {'ABORT', 16#fb, 1, false, 3, [a], abort, "Abort execution (dont use all gas) with error message in Arg0."}
, {'EXIT', 16#fc, 1, false, 3, [a], exit, "Abort execution (use upp all gas) with error message in Arg0."}
, { 'NOP', 16#fd, 0, false, 1, atomic, nop, "The no op. does nothing."}
%% FUNCTION 16#fe "Function declaration and entrypoint."
%% EXTEND 16#ff "Reserved for future extensions beyond one byte opcodes."
].
generate_header_file(Filename, Ops) ->
{ok, File} = file:open(Filename, [write]),
Defines = lists:flatten([gen_defines(Op) || Op <- Ops]),
io:format(File, "~s", [prelude("Provides opcode defines.\n")]),
io:format(File, "%% FATE opcodes\n~s", [Defines]),
io:format(File, "~s",
["-define('FUNCTION' , 16#fe).\n"
"-define('EXTEND' , 16#ff).\n\n"]),
file:close(File).
generate_opcodes_ops(Modulename, HrlFile, SrcDir, Ops) ->
Filename = SrcDir ++ atom_to_list(Modulename) ++ ".erl",
{ok, File} = file:open(Filename, [write]),
Mnemonic = lists:flatten([gen_mnemonic(Op) || Op <- Ops]),
ToOp = lists:flatten([gen_m_to_op(Op) || Op <- Ops]),
Args = lists:flatten([gen_args(Op) || Op <- Ops]),
EndBB = lists:flatten([gen_bb(Op) || Op <- Ops]),
io:format(File, "~s", [prelude("Provides opcode primitives.\n")]),
io:format(File, "~s", [ops_exports(Modulename, HrlFile,
["args/1\n"
" , end_bb/1\n"
" , mnemonic/1\n"
" , m_to_op/1\n"
])]),
io:format(File, "%% FATE mnemonics\n~s", [Mnemonic]),
io:format(File, "mnemonic(Op) -> exit({bad_opcode, Op}).\n\n", []),
io:format(File, "%% FATE opcodes\n~s", [ToOp]),
io:format(File, "m_to_op(M) -> exit({bad_mnemonic, M}).\n\n", []),
io:format(File, "%% FATE numbers of args to op.\n~s", [Args]),
io:format(File, "args(Op) -> exit({bad_opcode, Op}).\n\n", []),
io:format(File, "%% Does FATE Op end a Basic Block?\n~s", [EndBB]),
io:format(File, "end_bb(_) -> false.\n\n", []),
file:close(File).
generate_code_ops(Modulename, SrcDir, Ops) ->
Filename = SrcDir ++ atom_to_list(Modulename) ++ ".erl",
{ok, File} = file:open(Filename, [write]),
Types = lists:flatten([gen_type(Op) || Op <- Ops]),
TypeExports = lists:flatten([gen_type_exports(Op) || Op <- Ops]),
[#{type_name := FirstType} | RestOfOps] = Ops,
FateTypes = lists:flatten([gen_fate_code_type(Op) || Op <- RestOfOps]),
ConstructorExports = lists:flatten([gen_constructor_exports(Op) || Op <- Ops]),
Constructors = lists:flatten([gen_constructors(Op) || Op <- Ops]),
io:format(File, "~s", [prelude(" Provide constructor functuions for "
"Fate instructions.\n%%% Provide types"
" and documentation for Fate "
"instructions.\n")]),
io:format(File, "-module(~w).\n\n", [Modulename]),
io:format(File, "-include_lib(\"aebytecode/include/aeb_fate_data.hrl\").\n\n"
"-define(i(__X__), {immediate, __X__ }).\n\n"
"-type fate_arg_immediate(T) :: {immediate, T}.\n"
"-type fate_arg_var() :: {var, integer()}.\n"
"-type fate_arg_arg() :: {arg, integer()}.\n"
"-type fate_arg_stack() :: {stack, integer()}.\n"
"-type fate_arg() :: fate_arg_immediate()\n"
" | fate_arg_var()\n"
" | fate_arg_arg()\n"
" | fate_arg_stack().\n\n"
"-type fate_arg_immediate() :: {immediate, aeb_fate_data:fate_type()}.\n"
, []),
io:format(File, "~s", [Types]),
io:format(File, "-type fate_code() :: ~s\n~s .\n\n",
[FirstType, FateTypes]),
io:format(File, "-export_type([ fate_code/0\n~s ]).\n\n", [TypeExports]),
io:format(File, "-export([ foo/0\n~s ]).\n\n", [ConstructorExports]),
io:format(File, "~s\n", [Constructors]),
io:format(File, "foo() -> \"A temp hack.\".\n", []),
file:close(File).
gen_type(#{type_name := TypeName, type := Type}) ->
lists:flatten(io_lib:format("-type ~-26s :: ~s.\n",
[TypeName, Type])).
gen_fate_code_type(#{type_name := TypeName}) ->
lists:flatten(io_lib:format(" | ~s\n", [TypeName])).
gen_type_exports(#{type_name := TypeName}) ->
lists:flatten(io_lib:format(" , ~s/0\n", [TypeName--"()"])).
gen_constructor_exports(#{constructor_type := Function}) ->
lists:flatten(io_lib:format(" , ~s\n", [Function])).
gen_constructors(#{constructor := Function, format := atomic,
type_name := Type, opname := Name}) ->
lists:flatten(io_lib:format("-spec ~s() -> ~s.\n"
"~s() ->\n"
" ~w.\n\n",
[Function, Type, Function, Name]));
gen_constructors(#{constructor := Function, format := ArgSpec,
type_name := Type, opname := Name}) ->
ArgTypeSpecs = gen_arg_type_specs(ArgSpec),
Args = gen_arg_names(0, ArgSpec),
UseArgs = gen_arg_uses(0, ArgSpec),
lists:flatten(io_lib:format("-spec ~s(~s) -> ~s.\n"
"~s(~s) ->\n"
" {~w, ~s}.\n\n",
[Function, ArgTypeSpecs, Type,
Function, Args, Name, UseArgs])).
gen_arg_type_specs([]) -> [];
gen_arg_type_specs([a]) -> "fate_arg()";
gen_arg_type_specs([is]) -> "aeb_fate_data:fate_string()";
gen_arg_type_specs([ii]) -> "aeb_fate_data:fate_integer()";
gen_arg_type_specs([li]) -> "[aeb_fate_data:fate_integer()]";
gen_arg_type_specs([t]) -> "aeb_fate_data:fate_type_type()";
gen_arg_type_specs([a | Args]) -> "fate_arg(), " ++ gen_arg_type_specs(Args);
gen_arg_type_specs([is | Args]) -> "aeb_fate_data:fate_string(), " ++ gen_arg_type_specs(Args);
gen_arg_type_specs([ii | Args]) -> "aeb_fate_data:fate_integer(), " ++ gen_arg_type_specs(Args);
gen_arg_type_specs([li | Args]) -> "[aeb_fate_data:fate_integer()], " ++ gen_arg_type_specs(Args);
gen_arg_type_specs([t | Args]) -> "aeb_fate_data:fate_type_type(), " ++ gen_arg_type_specs(Args).
gen_arg_names(_, []) ->
[];
gen_arg_names(N, [_]) -> io_lib:format("Arg~w", [N]);
gen_arg_names(N, [_|Args]) ->
io_lib:format("Arg~w, ", [N]) ++ gen_arg_names(N+1, Args).
gen_arg_uses(_, []) ->
[];
gen_arg_uses(N, [a]) -> io_lib:format("Arg~w", [N]);
gen_arg_uses(N, [is]) -> io_lib:format("{immediate, Arg~w}", [N]);
gen_arg_uses(N, [ii]) -> io_lib:format("{immediate, Arg~w}", [N]);
gen_arg_uses(N, [li]) -> io_lib:format("[{immediate, I} || I <- Arg~w]", [N]);
gen_arg_uses(N, [t]) -> io_lib:format("Arg~w", [N]);
gen_arg_uses(N, [a | Args]) ->
io_lib:format("Arg~w, ", [N]) ++ gen_arg_uses(N+1, Args);
gen_arg_uses(N, [is | Args]) ->
io_lib:format("{immediate, Arg~w}, ", [N]) ++ gen_arg_uses(N+1, Args);
gen_arg_uses(N, [ii | Args]) ->
io_lib:format("{immediate, Arg~w}, ", [N]) ++ gen_arg_uses(N+1, Args);
gen_arg_uses(N, [li | Args]) ->
io_lib:format("[{immediate, I} || I <- Arg~w], ", [N]) ++ gen_arg_uses(N+1, Args);
gen_arg_uses(N, [t | Args]) ->
io_lib:format("Arg~w, ", [N]) ++ gen_arg_uses(N+1, Args).
ops_exports(Module, HrlFile, Exports) ->
lists:flatten(io_lib:format(
"-module(~w).\n\n"
"-export([ ~s ]).\n\n"
"-include_lib(\"aebytecode/" ++ HrlFile ++"\").\n\n"
"%%====================================================================\n"
"%% API\n"
"%%====================================================================\n",
[Module, Exports])).
gen_mnemonic(#{opname := Name, macro := Macro}) ->
lists:flatten(io_lib:format("mnemonic(~21s) -> ~21w ;\n",
[Macro, Name])).
gen_m_to_op(#{opname := Name, macro := Macro}) ->
lists:flatten(io_lib:format("m_to_op(~21w) -> ~21s ;\n",
[Name, Macro])).
gen_args(#{macro := Macro, args := Args}) ->
lists:flatten(io_lib:format("args(~21s) -> ~2w ;\n",
[Macro, Args])).
gen_bb(#{macro := Macro, end_bb := EndBB}) ->
lists:flatten(io_lib:format("end_bb(~21s) -> ~w ;\n",
[Macro, EndBB])).
prelude(Doc) ->
"%%%-------------------------------------------------------------------\n"
"%%% @copyright (C) 2019, Aeternity Anstalt\n"
"%%%\n"
"%%% === === N O T E : This file is generated do not edit. === ===\n"
"%%%\n"
"%%% Source is in aeb_fate_generate_ops.erl\n"
"%%% @doc\n"
"%%% "++Doc++
"%%% @end\n"
"%%%-------------------------------------------------------------------\n\n".
gen_defines(#{opname := Name, opcode := OpCode}) ->
lists:flatten(io_lib:format("-define(~-26w, 16#~2.16.0b).\n", [Name, OpCode])).
gen([]) ->
[];
gen([{OpName, OpCode, Args, EndBB, Gas, FateFormat, Constructor, Doc} | Rest]) ->
Name = atom_to_list(OpName),
LowerName = string:to_lower(Name),
TypeName = "fate_" ++ LowerName ++ "()",
Macro = "?" ++ Name,
Type = case FateFormat of
atomic -> io_lib:format("~w", [OpName]);
ArgTypes ->
io_lib:format("{~w, ~s}", [OpName, expand_types(ArgTypes)])
end,
ConstructorType = atom_to_list(Constructor) ++ "/" ++ io_lib:format("~w", [Args]),
[#{ opname => OpName
, opcode => OpCode
, args => Args
, end_bb => EndBB
, format => FateFormat
, macro => Macro
, type_name => TypeName
, doc => Doc
, gas => Gas
, type => Type
, constructor => Constructor
, constructor_type => ConstructorType
}| gen(Rest)].
expand_types([]) -> "";
expand_types([T]) -> expand_type(T);
expand_types([T|Ts]) ->expand_type(T) ++ ", " ++ expand_types(Ts).
expand_type(a) -> "fate_arg()";
expand_type(is) -> "fate_arg_immediate(aeb_fate_data:fate_string())";
expand_type(ii) -> "fate_arg_immediate(aeb_fate_data:fate_integer())";
expand_type(li) -> "[fate_arg_immediate(aeb_fate_data:fate_integer())]";
expand_type(t) -> "aeb_fate_data:fate_type_type()".
generate_scanner(TemplateFile, Outfile, Path, Ops) ->
{ok, Template} = file:read_file(filename:join(Path,TemplateFile)),
Tokens = lists:flatten([gen_token(Op) || Op <- Ops]),
NewFile = insert_tokens_in_template(Template, Tokens),
file:write_file(filename:join(Path, Outfile), NewFile).
gen_token(#{opname := OpName}) ->
Name = atom_to_list(OpName),
io_lib:format("~-28s: {token, {mnemonic, TokenLine, ~w}}.\n",
[Name, OpName]).
insert_tokens_in_template(<<"###REPLACEWITHOPTOKENS###", Rest/binary >>, Tokens) ->
[Tokens, Rest];
insert_tokens_in_template(<<"###REPLACEWITHNOTE###", Rest/binary >>, Tokens) ->
[
"%%%\n"
"%%% === === N O T E : This file is generated do not edit. === ===\n"
"%%%\n"
"%%% Source is in aeb_fate_generate_ops.erl\n"
"%%% and aeb_fate_asm_scan.template"
| insert_tokens_in_template(Rest, Tokens)];
insert_tokens_in_template(<<B,Rest/binary>>, Tokens) ->
[B|insert_tokens_in_template(Rest, Tokens)].
+6 -3
View File
@@ -1,10 +1,13 @@
{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.1"},
{registered, []},
{applications,
[kernel,
stdlib
stdlib,
eblake2,
aeserialization,
getopt
]},
{env,[]},
{modules, []},
+57
View File
@@ -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.
+12
View File
@@ -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).
+65
View File
@@ -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).
+82
View File
@@ -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">>)})
].
+26
View File
@@ -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
+14
View File
@@ -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
+26
View File
@@ -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
+8
View File
@@ -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", []).
+11
View File
@@ -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
+33
View File
@@ -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
+31
View File
@@ -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
+4
View File
@@ -0,0 +1,4 @@
;; CONTRACT remote
FUNCTION add_five(integer):integer
ADD a 5 arg0
RETURN
+45
View File
@@ -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, []).
+35
View File
@@ -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