Add serialization/deserialization.

This commit is contained in:
Erik Stenman 2019-02-13 15:35:48 +01:00
parent c4adf27a6e
commit 286a8c1913
8 changed files with 223 additions and 24 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ rel/example_project
.concrete/DEV_MODE
.rebar
aeb_asm_scan.erl
aefa_asm_scan.erl

View File

@ -79,9 +79,9 @@
-define('ABORT' , 16#50).
-define('EXIT' , 16#51).
-define('DEACTIVATE' , 16#52).
-define('INT_TO_STR' , 16#53).
-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).
@ -101,6 +101,7 @@
-define('SHA256' , 16#66).
-define('BLAKE2B' , 16#67).
-define('FUNCTION' , 16#fe).
-define('EXTEND' , 16#ff).
-define( COMMENT(X), {comment, X}).

View File

@ -2,7 +2,10 @@
{erl_opts, [debug_info]}.
{deps, []}.
{deps, [
{enacl, {git, "https://github.com/aeternity/enacl.git",
{ref, "26180f4"}}}
]}.
{dialyzer, [
{warnings, [unknown]},
@ -11,7 +14,7 @@
]}.
{relx, [{release, {aessembler, "0.0.1"},
[aebytecode]},
[aebytecode, enacl]},
{dev_mode, true},
{include_erts, false},

View File

@ -1 +1,4 @@
[].
[{<<"enacl">>,
{git,"https://github.com/aeternity/enacl.git",
{ref,"26180f42c0b3a450905d2efd8bc7fd5fd9cece75"}},
0}].

View File

@ -43,12 +43,15 @@
-module(aefa_asm).
-export([ file/2
-export([ asm_to_bytecode/2
, bytecode_to_fate_code/2
, pp/1
, read_file/1
, to_hexstring/1
]).
-include_lib("aebytecode/include/aefa_opcodes.hrl").
-define(HASH_BYTES, 32).
pp(Asm) ->
@ -60,17 +63,18 @@ format(Asm) -> format(Asm, 0).
format([{comment, Comment} | Rest], Address) ->
";; " ++ Comment ++ "\n" ++ format(Rest, Address);
format([Mnemonic | Rest], Address) ->
_Op = aefa_opcode:m_to_op(Mnemonic),
_Op = aefa_opcodes:m_to_op(Mnemonic),
" " ++ atom_to_list(Mnemonic) ++ "\n"
++ format(Rest, Address + 1);
format([],_) -> [].
file(Filename, Options) ->
read_file(Filename) ->
{ok, File} = file:read_file(Filename),
{ok, Tokens, _} = aefa_asm_scan:scan(binary_to_list(File)),
binary_to_list(File).
asm_to_bytecode(AssemblerCode, Options) ->
{ok, Tokens, _} = aefa_asm_scan:scan(AssemblerCode),
case proplists:lookup(pp_tokens, Options) of
{pp_tokens, true} ->
@ -79,7 +83,9 @@ file(Filename, Options) ->
ok
end,
Env = to_bytecode(Tokens, none, #{}, [], Options),
Env = to_bytecode(Tokens, none, #{ functions => #{}
, symbols => #{}
}, [], Options),
ByteList = serialize(Env),
@ -92,11 +98,141 @@ file(Filename, Options) ->
{Env, list_to_binary(ByteList)}.
serialize(Env) ->
bytecode_to_fate_code(ByteCode,_Options) ->
deserialize(ByteCode, #{ function => none
, bb => 0
, current_bb_code => []
, functions => #{}
, code => #{}
}).
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
, bb := BB
, current_bb_code := Code
, code := Program
, functions := Funs} = Env) ->
{Sig, Rest2} = deserialize_signature(Rest),
case Code of
[] ->
Env2 = Env#{ bb => 0
, current_bb_code => []
, function => {<<A,B,C,D>>, Sig}
, code => #{}
, functions => Funs#{F => Program}},
deserialize(Rest2, Env2);
_ ->
Env2 = Env#{ bb => 0
, current_bb_code => []
, function => {<<A,B,C,D>>, Sig}
, code => #{}
, functions =>
Funs#{F => 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 aefa_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
, 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 => FunctionCode}}.
deserialize_op(Op, Rest, Code) ->
OpName = aefa_opcodes:mnemonic(Op),
case aefa_opcodes:args(Op) of
0 -> {Rest, [OpName | Code]};
1 -> %% TODO: use rlp encoded int.
<<Arg:8, Rest2/binary>> = Rest,
{Rest2, [Arg, OpName | Code]};
hash ->
<<A, B, C, D, Rest2/binary>> = Rest,
Code2 = [<<A,B,C,D>>, OpName | Code],
{Rest2, Code2}
end.
serialize(#{functions := Functions} = Env) ->
%% TODO: add serialization of immediates
%% TODO: add serialization of function definitions
Code = [C || {_Name, {_Sig, C}} <- maps:to_list(Env)],
Code.
Code = [[?FUNCTION, Name, serialize_signature(Sig), C] ||
{Name, {Sig, C}} <- maps:to_list(Functions)],
lists:flatten(Code).
serialize_signature({Args, RetType}) ->
[serialize_type({tuple, Args}) |
serialize_type(RetType)].
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)].
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(
@ -108,7 +244,7 @@ to_bytecode([{function,_line, 'FUNCTION'}|Rest], Address, Env, Code, Opts) ->
{Fun, Rest2} = to_fun_def(Rest),
to_bytecode(Rest2, Fun, Env2, [], Opts);
to_bytecode([{mnemonic,_line, Op}|Rest], Address, Env, Code, Opts) ->
OpCode = aefa_opcode:m_to_op(Op),
OpCode = aefa_opcodes:m_to_op(Op),
%% TODO: arguments
to_bytecode(Rest, Address, Env, [OpCode|Code], Opts);
to_bytecode([{int,_line, Int}|Rest], Address, Env, Code, Opts) ->
@ -116,7 +252,8 @@ to_bytecode([{int,_line, Int}|Rest], Address, Env, Code, Opts) ->
to_bytecode([{hash,_line, Hash}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env, [Hash|Code], Opts);
to_bytecode([{id,_line, ID}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env, [{ref, ID}|Code], Opts);
{ok, Hash} = lookup_symbol(ID, Env),
to_bytecode(Rest, Address, Env, [Hash|Code], Opts);
to_bytecode([{label,_line, Label}|Rest], Address, Env, Code, Opts) ->
to_bytecode(Rest, Address, Env#{Label => Address}, Code, Opts);
to_bytecode([], Address, Env, Code, Opts) ->
@ -190,5 +327,26 @@ expand_args([OP | Rest]) ->
expand_args([]) -> [].
insert_fun(none, [], Env) -> Env;
insert_fun({Name, Type, RetType}, Code, Env) ->
Env#{Name => {{Type, RetType}, lists:reverse(Code)}}.
insert_fun({Name, Type, RetType}, Code, #{functions := Functions} = Env) ->
{Hash, Env2} = insert_symbol(Name, Env),
Env2#{
functions => Functions#{Hash => {{Type, RetType}, lists:reverse(Code)}}
}.
insert_symbol(Id, Env) ->
%% Use first 4 bytes of blake hash
{ok, <<A:8, B:8, C:8, D:8,_/binary>> } = enacl:generichash(?HASH_BYTES, list_to_binary(Id)),
insert_symbol(Id, <<A,B,C,D>>, 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.
lookup_symbol(Id, #{symbols := Symbols} = Env) ->
maps:find(Id, Symbols).

View File

@ -47,6 +47,9 @@ 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'}}.
LT : {token, {mnemonic, TokenLine, 'LT'}}.
GT : {token, {mnemonic, TokenLine, 'GT'}}.
EQ : {token, {mnemonic, TokenLine, 'EQ'}}.

View File

@ -5,9 +5,11 @@
%%% @end
%%%-------------------------------------------------------------------
-module(aefa_opcode).
-module(aefa_opcodes).
-export([ mnemonic/1
-export([ args/1
, end_bb/1
, mnemonic/1
, m_to_op/1
, opcode/1
]).
@ -23,6 +25,12 @@ opcode(X) when X >= 0, X =< 255 -> X;
opcode({comment,X}) -> ?COMMENT(X).
mnemonic(?NOP) -> 'NOP' ;
mnemonic(?RETURN) -> 'RETURN' ;
mnemonic(?PUSH) -> 'PUSH' ;
mnemonic(?JUMP) -> 'JUMP' ;
mnemonic(?INC) -> 'INC' ;
mnemonic(?CALL) -> 'CALL' ;
mnemonic(OP) -> {OP, nothandled} ;
mnemonic({comment,_}) -> 'COMMENT' .
m_to_op('NOP') -> ?NOP ;
@ -30,5 +38,20 @@ m_to_op('COMMENT') -> ?COMMENT("") ;
m_to_op('RETURN') -> ?RETURN ;
m_to_op('PUSH') -> ?PUSH ;
m_to_op('JUMP') -> ?JUMP ;
m_to_op('INC') -> ?INC ;
m_to_op('CALL') -> ?CALL ;
m_to_op(Data) when 0=<Data, Data=<255 -> Data.
args(?NOP) -> 0;
args(?RETURN) -> 0;
args(?PUSH) -> 1;
args(?JUMP) -> 1;
args(?INC) -> 0;
args(?CALL) -> hash;
args(_) -> 0. %% TODO do not allow this
end_bb(?RETURN) -> true;
end_bb(?JUMP) -> true;
end_bb(?CALL) -> true;
end_bb(_) -> false.

View File

@ -13,7 +13,13 @@ FUNCTION jumps() -> integer
JUMP 1
FUNCTION inc(integer) -> integer
INC arg0
INC
INC
RETURN
FUNCTION call(integer) -> integer
INC
CALL inc
INC
RETURN
@ -21,3 +27,4 @@ FUNCTION inc(integer) -> integer
;; _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, []).