From 878140e03ca9bae673d29a1a0014cebea8f1267e Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 15 Nov 2019 12:17:11 +0100 Subject: [PATCH 01/35] Add function to validate byte code against source code --- src/aeso_compiler.erl | 82 +++++++++++++++++++++++++++++ test/aeso_compiler_tests.erl | 49 +++++++++++++++++ test/contracts/validation_test1.aes | 4 ++ test/contracts/validation_test2.aes | 4 ++ test/contracts/validation_test3.aes | 4 ++ 5 files changed, 143 insertions(+) create mode 100644 test/contracts/validation_test1.aes create mode 100644 test/contracts/validation_test2.aes create mode 100644 test/contracts/validation_test3.aes diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index a2ec0bc..d780e49 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -23,6 +23,7 @@ , decode_calldata/4 , parse/2 , add_include_path/2 + , validate_byte_code/3 ]). -include_lib("aebytecode/include/aeb_opcodes.hrl"). @@ -558,6 +559,87 @@ pp(Code, Options, Option, PPFun) -> ok end. +%% -- Byte code validation --------------------------------------------------- + +-define(protect(Tag, Code), fun() -> try Code catch _:Err1 -> throw({Tag, Err1}) end end()). + +-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}. +validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) -> + Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end, + case proplists:get_value(backend, Options, aevm) of + B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B])); + fate -> + try + FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))), + {FCode2, SrcPayable} = + ?protect(compile, + begin + {ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} = + from_string1(fate, Source, Options), + FCode = aeb_fate_code:deserialize(SrcByteCode), + {aeb_fate_code:strip_init_function(FCode), SrcPayable} + end), + case compare_fate_code(FCode1, FCode2) of + ok when SrcPayable /= Payable -> + Not = fun(true) -> ""; (false) -> " not" end, + Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n", + [Not(Payable), Not(SrcPayable)])); + ok -> ok; + {error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why])) + end + catch + throw:{deserialize, _} -> Fail("Invalid byte code"); + throw:{compile, {error, Errs}} -> {error, Errs} + end + end. + +compare_fate_code(FCode1, FCode2) -> + Funs1 = aeb_fate_code:functions(FCode1), + Funs2 = aeb_fate_code:functions(FCode2), + Syms1 = aeb_fate_code:symbols(FCode1), + Syms2 = aeb_fate_code:symbols(FCode2), + FunHashes1 = maps:keys(Funs1), + FunHashes2 = maps:keys(Funs2), + case FunHashes1 == FunHashes2 of + false -> + InByteCode = [ binary_to_list(maps:get(H, Syms1)) || H <- FunHashes1 -- FunHashes2 ], + InSourceCode = [ binary_to_list(maps:get(H, Syms2)) || H <- FunHashes2 -- FunHashes1 ], + Msg = [ io_lib:format("- Functions in the byte code but not in the source code:\n" + " ~s\n", [string:join(InByteCode, ", ")]) || InByteCode /= [] ] ++ + [ io_lib:format("- Functions in the source code but not in the byte code:\n" + " ~s\n", [string:join(InSourceCode, ", ")]) || InSourceCode /= [] ], + {error, Msg}; + true -> + case lists:append([ compare_fate_fun(maps:get(H, Syms1), Fun1, Fun2) + || {{H, Fun1}, {_, Fun2}} <- lists:zip(maps:to_list(Funs1), + maps:to_list(Funs2)) ]) of + [] -> ok; + Errs -> {error, Errs} + end + end. + +compare_fate_fun(_Name, Fun, Fun) -> []; +compare_fate_fun(Name, {Attr, Type, _}, {Attr, Type, _}) -> + [io_lib:format("- The implementation of the function ~s is different.\n", [Name])]; +compare_fate_fun(Name, {Attr1, Type, _}, {Attr2, Type, _}) -> + [io_lib:format("- The attributes of the function ~s differ:\n" + " Byte code: ~s\n" + " Source code: ~s\n", + [Name, string:join([ atom_to_list(A) || A <- Attr1 ], ", "), + string:join([ atom_to_list(A) || A <- Attr2 ], ", ")])]; +compare_fate_fun(Name, {_, Type1, _}, {_, Type2, _}) -> + [io_lib:format("- The type of the function ~s differs:\n" + " Byte code: ~s\n" + " Source code: ~s\n", + [Name, pp_fate_sig(Type1), pp_fate_sig(Type2)])]. + +pp_fate_sig({[Arg], Res}) -> + io_lib:format("~s => ~s", [pp_fate_type(Arg), pp_fate_type(Res)]); +pp_fate_sig({Args, Res}) -> + io_lib:format("(~s) => ~s", [string:join([pp_fate_type(Arg) || Arg <- Args], ", "), pp_fate_type(Res)]). + +pp_fate_type(T) -> io_lib:format("~w", [T]). + %% ------------------------------------------------------------------- sophia_type_to_typerep(String) -> diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index afa4e6a..50130e6 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -12,6 +12,14 @@ -include_lib("eunit/include/eunit.hrl"). +run_test(Test) -> + TestFun = list_to_atom(lists:concat([Test, "_test_"])), + [ begin + io:format("~s\n", [Label]), + Fun() + end || {Label, Fun} <- ?MODULE:TestFun() ], + ok. + %% Very simply test compile the given contracts. Only basic checks %% are made on the output, just that it is a binary which indicates %% that the compilation worked. @@ -702,3 +710,44 @@ failing_code_gen_contracts() -> "The state cannot contain functions in the AEVM. Use FATE if you need this.") ]. +validation_test_() -> + [{"Validation fail: " ++ C1 ++ " /= " ++ C2, + fun() -> + Actual = case validate(C1, C2) of + {error, Errs} -> Errs; + ok -> #{} + end, + check_errors(Expect, Actual) + end} || {C1, C2, Expect} <- validation_fails()] ++ + [{"Validation of " ++ C, + fun() -> + ?assertEqual(ok, validate(C, C)) + end} || C <- compilable_contracts()]. + +validation_fails() -> + [{"deadcode", "nodeadcode", + [<<"Data error:\n" + "Byte code does not match source code.\n" + "- Functions in the source code but not in the byte code:\n" + " .MyList.map2">>]}, + {"validation_test1", "validation_test2", + [<<"Data error:\n" + "Byte code does not match source code.\n" + "- The implementation of the function code_fail is different.\n" + "- The attributes of the function attr_fail differ:\n" + " Byte code: payable\n" + " Source code: \n" + "- The type of the function type_fail differs:\n" + " Byte code: integer => integer\n" + " Source code: {tvar,0} => {tvar,0}">>]}, + {"validation_test1", "validation_test3", + [<<"Data error:\n" + "Byte code contract is not payable, but source code contract is.">>]}]. + +validate(Contract1, Contract2) -> + ByteCode = #{ fate_code := FCode } = compile(fate, Contract1), + FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)), + Source = aeso_test_utils:read_contract(Contract2), + aeso_compiler:validate_byte_code(ByteCode#{ byte_code := FCode1 }, Source, + [{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]). + diff --git a/test/contracts/validation_test1.aes b/test/contracts/validation_test1.aes new file mode 100644 index 0000000..75b43d1 --- /dev/null +++ b/test/contracts/validation_test1.aes @@ -0,0 +1,4 @@ +contract ValidationTest = + payable entrypoint attr_fail() = () + entrypoint type_fail(x : int) = x + entrypoint code_fail(x) = x + 1 diff --git a/test/contracts/validation_test2.aes b/test/contracts/validation_test2.aes new file mode 100644 index 0000000..f77334e --- /dev/null +++ b/test/contracts/validation_test2.aes @@ -0,0 +1,4 @@ +contract ValidationTest = + entrypoint attr_fail() = () + entrypoint type_fail(x) = x + entrypoint code_fail(x) = x - 1 diff --git a/test/contracts/validation_test3.aes b/test/contracts/validation_test3.aes new file mode 100644 index 0000000..36af27d --- /dev/null +++ b/test/contracts/validation_test3.aes @@ -0,0 +1,4 @@ +payable contract ValidationTest = + payable entrypoint attr_fail() = () + entrypoint type_fail(x : int) = x + entrypoint code_fail(x) = x + 1 From f42353b300686242294b33c68d067792989e9a47 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 18 Nov 2019 11:35:08 +0100 Subject: [PATCH 02/35] Handle encoding/decoding bits Fixes GH-174 --- src/aeso_compiler.erl | 8 ++++++++ src/aeso_fcode_to_fate.erl | 12 +++++++++++ src/aeso_vm_decode.erl | 32 ++++++++++++++++++++++++----- test/aeso_calldata_tests.erl | 40 +++++++++++++++++++++++++----------- 4 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index d780e49..6327f00 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -508,6 +508,14 @@ icode_to_term(T = {map, KT, VT}, M) -> #{}; _ -> throw({todo, M}) end; +icode_to_term(word, {unop, 'bnot', A}) -> + bnot icode_to_term(word, A); +icode_to_term(word, {binop, 'bor', A, B}) -> + icode_to_term(word, A) bor icode_to_term(word, B); +icode_to_term(word, {binop, 'bsl', A, B}) -> + icode_to_term(word, B) bsl icode_to_term(word, A); +icode_to_term(word, {binop, 'band', A, B}) -> + icode_to_term(word, A) band icode_to_term(word, B); icode_to_term(typerep, _) -> throw({todo, typerep}); icode_to_term(T, V) -> diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 7c399b9..59ccd9f 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -261,6 +261,18 @@ term_to_fate({tuple, As}) -> term_to_fate({con, Ar, I, As}) -> FateAs = [ term_to_fate(A) || A <- As ], aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs)); +term_to_fate({builtin, bits_all, []}) -> + aeb_fate_data:make_bits(-1); +term_to_fate({builtin, bits_none, []}) -> + aeb_fate_data:make_bits(0); +term_to_fate({op, bits_set, [B, I]}) -> + {bits, N} = term_to_fate(B), + J = term_to_fate(I), + {bits, N bor (1 bsl J)}; +term_to_fate({op, bits_clear, [B, I]}) -> + {bits, N} = term_to_fate(B), + J = term_to_fate(I), + {bits, N band bnot (1 bsl J)}; term_to_fate({builtin, map_empty, []}) -> aeb_fate_data:make_map(#{}); term_to_fate({'let', _, {builtin, map_empty, []}, Set}) -> diff --git a/src/aeso_vm_decode.erl b/src/aeso_vm_decode.erl index bf61a48..827aed4 100644 --- a/src/aeso_vm_decode.erl +++ b/src/aeso_vm_decode.erl @@ -18,9 +18,14 @@ from_aevm(word, {id, _, "address"}, N) -> address_literal(ac from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N); from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N); from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N); -from_aevm(word, {id, _, "int"}, N) -> <> = <>, {int, [], N1}; -from_aevm(word, {id, _, "bits"}, N) -> error({todo, bits, N}); -from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0}; +from_aevm(word, {id, _, "int"}, N0) -> + <> = <>, + if N < 0 -> {app, [], {'-', []}, [{int, [], -N}]}; + true -> {int, [], N} end; +from_aevm(word, {id, _, "bits"}, N0) -> + <> = <>, + make_bits(N); +from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0}; from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 -> <> = <>, {bytes, [], <>}; @@ -55,6 +60,7 @@ from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args}) VmTypes = lists:nth(Tag + 1, VmCons), ConType = lists:nth(Tag + 1, Cons), from_aevm(VmTypes, ConType, Args); +from_aevm([], {constr_t, _, Con, []}, []) -> Con; from_aevm(VmTypes, {constr_t, _, Con, Types}, Args) when length(VmTypes) == length(Types), length(VmTypes) == length(Args) -> @@ -70,8 +76,10 @@ from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin}; from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin}; -from_fate({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin}); -from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N}; +from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N); +from_fate({id, _, "int"}, N) when is_integer(N) -> + if N < 0 -> {app, [], {'-', []}, [{int, [], -N}]}; + true -> {int, [], N} end; from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B}; from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S}; from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) -> @@ -105,9 +113,23 @@ from_fate({variant_t, Cons}, {variant, Ar, Tag, Args}) from_fate(ConType, ArgList); _ -> throw(cannot_translate_to_sophia) end; +from_fate({constr_t, _, Con, []}, []) -> Con; from_fate({constr_t, _, Con, Types}, Args) when length(Types) == length(Args) -> {app, [], Con, [ from_fate(Type, Arg) || {Type, Arg} <- lists:zip(Types, Args) ]}; from_fate(_Type, _Data) -> throw(cannot_translate_to_sophia). + + +make_bits(N) -> + Id = fun(F) -> {qid, [], ["Bits", F]} end, + if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N); + true -> make_bits(Id("set"), Id("none"), 0, N) end. + +make_bits(_Set, Zero, _I, 0) -> Zero; +make_bits(Set, Zero, I, N) when 0 == N rem 2 -> + make_bits(Set, Zero, I + 1, N div 2); +make_bits(Set, Zero, I, N) -> + {app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}. + diff --git a/test/aeso_calldata_tests.erl b/test/aeso_calldata_tests.erl index 2f419cc..46e0be9 100644 --- a/test/aeso_calldata_tests.erl +++ b/test/aeso_calldata_tests.erl @@ -29,11 +29,10 @@ calldata_test_() -> true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]); false -> undefined end, - case FateExprs == undefined orelse AevmExprs == undefined of - true -> ok; - false -> - ?assertEqual(FateExprs, AevmExprs) - end + ParsedExprs = parse_args(Fun, Args), + [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ], + [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ], + ok end} || {ContractName, Fun, Args} <- compilable_contracts()]. calldata_aci_test_() -> @@ -53,19 +52,34 @@ calldata_aci_test_() -> true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]); false -> undefined end, - case FateExprs == undefined orelse AevmExprs == undefined of - true -> ok; - false -> - ?assertEqual(FateExprs, AevmExprs) - end + ParsedExprs = parse_args(Fun, Args), + [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ], + [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ], + ok end} || {ContractName, Fun, Args} <- compilable_contracts()]. +parse_args(Fun, Args) -> + [{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] = + aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"), + strip_ann(AST). + +strip_ann(T) when is_tuple(T) -> + strip_ann1(setelement(2, T, [])); +strip_ann(X) -> strip_ann1(X). + +strip_ann1({map, [], KVs}) -> + {map, [], [{strip_ann(K), strip_ann(V)} || {K, V} <- KVs]}; +strip_ann1(T) when is_tuple(T) -> + list_to_tuple(strip_ann1(tuple_to_list(T))); +strip_ann1(L) when is_list(L) -> + lists:map(fun strip_ann/1, L); +strip_ann1(X) -> X. ast_exprs(ContractString, Fun, Args, Opts) -> {ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)), {ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)), ?assert(is_list(Exprs)), - Exprs. + strip_ann(Exprs). check_errors(Expect, ErrorString) -> %% This removes the final single \n as well. @@ -85,7 +99,9 @@ compilable_contracts() -> {"maps", "init", []}, {"funargs", "menot", ["false"]}, {"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]}, - %% TODO {"funargs", "bitsum", ["Bits.all"]}, + {"funargs", "bitsum", ["Bits.all"]}, + {"funargs", "bitsum", ["Bits.clear(Bits.clear(Bits.all, 4), 2)"]}, %% Order matters for test + {"funargs", "bitsum", ["Bits.set(Bits.set(Bits.none, 4), 2)"]}, {"funargs", "read", ["{label = \"question 1\", result = 4}"]}, {"funargs", "sjutton", ["#0011012003100011012003100011012003"]}, {"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940" From 49f9ef955f9b165ffe453759a4a60bed166c74fe Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 18 Nov 2019 11:55:04 +0100 Subject: [PATCH 03/35] Prefix format annotation for negative numbers --- src/aeso_vm_decode.erl | 4 ++-- test/aeso_abi_tests.erl | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aeso_vm_decode.erl b/src/aeso_vm_decode.erl index 827aed4..d25512e 100644 --- a/src/aeso_vm_decode.erl +++ b/src/aeso_vm_decode.erl @@ -20,7 +20,7 @@ from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(or from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N); from_aevm(word, {id, _, "int"}, N0) -> <> = <>, - if N < 0 -> {app, [], {'-', []}, [{int, [], -N}]}; + if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]}; true -> {int, [], N} end; from_aevm(word, {id, _, "bits"}, N0) -> <> = <>, @@ -78,7 +78,7 @@ from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin}; from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N); from_fate({id, _, "int"}, N) when is_integer(N) -> - if N < 0 -> {app, [], {'-', []}, [{int, [], -N}]}; + if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]}; true -> {int, [], N} end; from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B}; from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S}; diff --git a/test/aeso_abi_tests.erl b/test/aeso_abi_tests.erl index ce04b33..6f1fb02 100644 --- a/test/aeso_abi_tests.erl +++ b/test/aeso_abi_tests.erl @@ -62,7 +62,7 @@ encode_decode_sophia_test() -> Other -> Other end end, ok = Check("int", "42"), - ok = Check("int", "-42"), + ok = Check("int", "- 42"), ok = Check("bool", "true"), ok = Check("bool", "false"), ok = Check("string", "\"Hello\""), From e8a54395bf5b35aa7e4479eb77483fb22e2ac557 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 6 Nov 2019 11:13:45 +0100 Subject: [PATCH 04/35] Export optimize_fun for tests --- src/aeso_fcode_to_fate.erl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 59ccd9f..bab3f53 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -11,6 +11,10 @@ -export([compile/2, term_to_fate/1]). +-ifdef(TEST). +-export([optimize_fun/4]). +-endif. + %% -- Preamble --------------------------------------------------------------- -type scode() :: [sinstr()]. From 47ad607dd5079f8d66a72241562fd4977db04e64 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 6 Nov 2019 11:14:28 +0100 Subject: [PATCH 05/35] Handle arbitrary store registers --- src/aeso_fcode_to_fate.erl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index bab3f53..7be8c13 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -717,11 +717,11 @@ pp_op(loop) -> "LOOP"; pp_op(I) -> aeb_fate_pp:format_op(I, #{}). -pp_arg(?i(I)) -> io_lib:format("~w", [I]); -pp_arg({arg, N}) -> io_lib:format("arg~p", [N]); -pp_arg(?s) -> "store1"; -pp_arg({var, N}) -> io_lib:format("var~p", [N]); -pp_arg(?a) -> "a". +pp_arg(?i(I)) -> io_lib:format("~w", [I]); +pp_arg({arg, N}) -> io_lib:format("arg~p", [N]); +pp_arg({store, N}) -> io_lib:format("store~p", [N]); +pp_arg({var, N}) -> io_lib:format("var~p", [N]); +pp_arg(?a) -> "a". %% -- Analysis -- @@ -1376,7 +1376,7 @@ desugar_args(I) when is_tuple(I) -> list_to_tuple([Op | lists:map(fun desugar_arg/1, Args)]); desugar_args(I) -> I. -desugar_arg(?s) -> {var, -1}; +desugar_arg({store, N}) -> {var, -N}; desugar_arg(A) -> A. %% -- Phase III -------------------------------------------------------------- From e6b5c5a526df8b01e60dc70f3bbf6139440950b8 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 6 Nov 2019 11:15:01 +0100 Subject: [PATCH 06/35] Fix bug in short-cut for IS_NIL --- src/aeso_fcode_to_fate.erl | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 7be8c13..e2cf45a 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1155,18 +1155,20 @@ r_swap_write(Pre, I, Code0 = [J | Code]) -> r_swap_write(_, _, _) -> false. %% Precompute instructions with known values -r_constant_propagation(Cons = {i, _, {'CONS', R, _, _}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) -> +r_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) -> Store = {i, Ann, {'STORE', S, ?i(false)}}, - case R of - ?a -> {[Store], Code}; - _ -> {[Cons, Store], Code} - end; -r_constant_propagation(Cons = {i, _, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) -> + Cons1 = case R of + ?a -> {i, Ann1, {'CONS', ?void, X, Xs}}; + _ -> Cons + end, + {[Cons1, Store], Code}; +r_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) -> Store = {i, Ann, {'STORE', S, ?i(true)}}, - case R of - ?a -> {[Store], Code}; - _ -> {[Cons, Store], Code} - end; + Nil1 = case R of + ?a -> {i, Ann1, {'NIL', ?void}}; + _ -> Nil + end, + {[Nil1, Store], Code}; r_constant_propagation({i, Ann, I}, Code) -> case op_view(I) of false -> false; From 1ca301895851feaacf365b0c513bc66090d39f29 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 6 Nov 2019 11:28:46 +0100 Subject: [PATCH 07/35] Don't run pretty printer if not pretty printing --- src/aeso_fcode_to_fate.erl | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index e2cf45a..6f94e5a 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -129,9 +129,12 @@ is_debug(Tag, Options) -> Tags = proplists:get_value(debug, Options, []), Tags == all orelse lists:member(Tag, Tags). -debug(Tag, Options, Fmt, Args) -> +-define(debug(Tag, Options, Fmt, Args), + debug(Tag, Options, fun() -> io:format(Fmt, Args) end)). + +debug(Tag, Options, Fun) -> case is_debug(Tag, Options) of - true -> io:format(Fmt, Args); + true -> Fun(); false -> ok end. @@ -144,7 +147,7 @@ compile(FCode, Options) -> SFuns = functions_to_scode(ContractName, Functions, Options), SFuns1 = optimize_scode(SFuns, Options), FateCode = to_basic_blocks(SFuns1), - debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), + ?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), FateCode. make_function_id(X) -> @@ -665,23 +668,23 @@ flatten_s(I) -> I. optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) -> Code0 = flatten(Code), - debug(opt, Options, "Optimizing ~s\n", [Name]), + ?debug(opt, Options, "Optimizing ~s\n", [Name]), Code1 = simpl_loop(0, Code0, Options), Code2 = desugar(Code1), {Attrs, Sig, Code2}. simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS -> - debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]), + ?debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]), Code; simpl_loop(N, Code, Options) -> ACode = annotate_code(Code), - [ debug(opt, Options, " annotated:\n~s\n", [pp_ann(" ", ACode)]) || N == 0 ], + [ ?debug(opt, Options, " annotated:\n~s\n", [pp_ann(" ", ACode)]) || N == 0 ], Code1 = simplify(ACode, Options), - [ debug(opt, Options, " optimized:\n~s\n", [pp_ann(" ", Code1)]) || Code1 /= ACode ], + [ ?debug(opt, Options, " optimized:\n~s\n", [pp_ann(" ", Code1)]) || Code1 /= ACode ], Code2 = unannotate(Code1), case Code == Code2 of true -> - debug(opt, Options, " Reached simpl_loop fixed point after ~p iteration~s.\n\n", + ?debug(opt, Options, " Reached simpl_loop fixed point after ~p iteration~s.\n\n", [N, if N /= 1 -> "s"; true -> "" end]), Code2; false -> simpl_loop(N + 1, Code2, Options) @@ -1011,7 +1014,7 @@ apply_rules(Fuel, Rules, I, Code, Options) -> case is_debug(opt_rules, Options) of true -> {OldCode, NewCode} = drop_common_suffix([I | Code], New ++ Rest), - debug(opt_rules, Options, " Applied ~p:\n~s ==>\n~s\n", [RName, pp_ann(" ", OldCode), pp_ann(" ", NewCode)]); + ?debug(opt_rules, Options, " Applied ~p:\n~s ==>\n~s\n", [RName, pp_ann(" ", OldCode), pp_ann(" ", NewCode)]); false -> ok end, lists:foldr(Cons, Rest, New) From eba4f1c79c0e319a75aa5efc0b017e0a72a9ce0f Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Wed, 6 Nov 2019 11:53:49 +0100 Subject: [PATCH 08/35] Call instructions read the function argument --- src/aeso_fcode_to_fate.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 6f94e5a..c604ed0 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -790,10 +790,10 @@ attributes(I) -> loop -> Impure(pc, []); 'RETURN' -> Impure(pc, []); {'RETURNR', A} -> Impure(pc, A); - {'CALL', _} -> Impure(?a, []); + {'CALL', A} -> Impure(?a, [A]); {'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]); {'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]); - {'CALL_T', _} -> Impure(pc, []); + {'CALL_T', A} -> Impure(pc, [A]); {'CALL_VALUE', A} -> Pure(A, []); {'JUMP', _} -> Impure(pc, []); {'JUMPIF', A, _} -> Impure(pc, A); From 13b196568b4fc05d8f257085d5bcc171cd97149b Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 10:12:30 +0100 Subject: [PATCH 09/35] Handle reads from undefined variables in liveness analysis Doesn't affect well-formed code, but makes testing easier. --- src/aeso_fcode_to_fate.erl | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index c604ed0..87c37e5 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -759,8 +759,7 @@ ann_reads([{switch, Arg, Type, Alts, Def} | Code], Reads, Acc) -> {Def1, ReadsDef} = ann_reads(Def, Reads, []), Reads1 = ordsets:union([[Arg], Reads, ReadsDef | ReadsAlts]), ann_reads(Code, Reads1, [{switch, Arg, Type, Alts1, Def1} | Acc]); -ann_reads([{i, Ann, I} | Code], Reads, Acc) -> - #{ writes_in := WritesIn, writes_out := WritesOut } = Ann, +ann_reads([{i, _Ann, I} | Code], Reads, Acc) -> #{ read := Rs, write := W, pure := Pure } = attributes(I), %% If we write it here it's not live in (unless we also read it) Reads1 = Reads -- [W], @@ -772,8 +771,8 @@ ann_reads([{i, Ann, I} | Code], Reads, Acc) -> {{var, _}, true} -> Reads1; _ -> ordsets:union(Reads1, Rs) end, - LiveIn = ordsets:intersection(Reads2, WritesIn), - LiveOut = ordsets:intersection(Reads, WritesOut), + LiveIn = Reads2, % For well-formed code this should be a subset of WritesIn + LiveOut = Reads, % and this of WritesOut, Ann1 = #{ live_in => LiveIn, live_out => LiveOut }, ann_reads(Code, Reads2, [{i, Ann1, I} | Acc]); ann_reads([], Reads, Acc) -> {Acc, Reads}. From aca6b89fcf6f373bcc91c3c15c480675aa04e10c Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 10:13:02 +0100 Subject: [PATCH 10/35] Store arguments are now separate from vars --- src/aeso_fcode_to_fate.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 87c37e5..0a57e59 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -743,7 +743,7 @@ ann_writes([{switch, Arg, Type, Alts, Def} | Code], Writes, Acc) -> Writes1 = ordsets:union(Writes, ordsets:intersection([WritesDef | WritesAlts])), ann_writes(Code, Writes1, [{switch, Arg, Type, Alts1, Def1} | Acc]); ann_writes([I | Code], Writes, Acc) -> - Ws = [ W || W <- var_writes(I), not ?IsState(W) ], + Ws = [ W || W <- var_writes(I) ], Writes1 = ordsets:union(Writes, Ws), Ann = #{ writes_in => Writes, writes_out => Writes1 }, ann_writes(Code, Writes1, [{i, Ann, I} | Acc]); @@ -969,7 +969,7 @@ swap_instrs({i, #{ live_in := Live1 }, I}, {i, #{ live_in := Live2, live_out := {{i, #{ live_in => Live1, live_out => Live2_ }, J}, {i, #{ live_in => Live2_, live_out => Live3 }, I}}. -live_in(R, _) when ?IsState(R) -> true; +live_in({store, _}, _) -> true; live_in(R, #{ live_in := LiveIn }) -> ordsets:is_element(R, LiveIn); live_in(R, {i, Ann, _}) -> live_in(R, Ann); live_in(R, [I = {i, _, _} | _]) -> live_in(R, I); @@ -979,7 +979,7 @@ live_in(R, [{switch, A, _, Alts, Def} | _]) -> live_in(_, missing) -> false; live_in(_, []) -> false. -live_out(R, _) when ?IsState(R) -> true; +live_out({store, _}, _) -> true; live_out(R, #{ live_out := LiveOut }) -> ordsets:is_element(R, LiveOut). %% -- Optimizations -- From a4b21063e326d130203d5eb3921c4a06a533b2cc Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 10:13:21 +0100 Subject: [PATCH 11/35] Get rid of IsOp --- src/aeso_fcode_to_fate.erl | 85 +++----------------------------------- 1 file changed, 5 insertions(+), 80 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 0a57e59..330d8a4 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -45,82 +45,6 @@ -define(s, {store, 1}). -define(void, {var, 9999}). --define(IsState(X), (is_tuple(X) andalso tuple_size(X) =:= 2 andalso element(1, X) =:= var andalso element(2, X) < 0)). - --define(IsOp(Op), ( - Op =:= 'STORE' orelse - Op =:= 'ADD' orelse - Op =:= 'SUB' orelse - Op =:= 'MUL' orelse - Op =:= 'DIV' orelse - Op =:= 'MOD' orelse - Op =:= 'POW' orelse - Op =:= 'LT' orelse - Op =:= 'GT' orelse - Op =:= 'EQ' orelse - Op =:= 'ELT' orelse - Op =:= 'EGT' orelse - Op =:= 'NEQ' orelse - Op =:= 'AND' orelse - Op =:= 'OR' orelse - Op =:= 'NOT' orelse - Op =:= 'ELEMENT' orelse - Op =:= 'MAP_EMPTY' orelse - Op =:= 'MAP_LOOKUP' orelse - Op =:= 'MAP_LOOKUPD' orelse - Op =:= 'MAP_UPDATE' orelse - Op =:= 'MAP_DELETE' orelse - Op =:= 'MAP_MEMBER' orelse - Op =:= 'MAP_FROM_LIST' orelse - Op =:= 'MAP_TO_LIST' orelse - Op =:= 'MAP_SIZE' orelse - Op =:= 'NIL' orelse - Op =:= 'IS_NIL' orelse - Op =:= 'CONS' orelse - Op =:= 'HD' orelse - Op =:= 'TL' orelse - Op =:= 'LENGTH' orelse - Op =:= 'APPEND' orelse - Op =:= 'STR_JOIN' orelse - Op =:= 'INT_TO_STR' orelse - Op =:= 'ADDR_TO_STR' orelse - Op =:= 'STR_REVERSE' orelse - Op =:= 'STR_LENGTH' orelse - Op =:= 'INT_TO_ADDR' orelse - Op =:= 'VARIANT_TEST' orelse - Op =:= 'VARIANT_ELEMENT' orelse - Op =:= 'BITS_NONE' orelse - Op =:= 'BITS_ALL' orelse - Op =:= 'BITS_ALL_N' orelse - Op =:= 'BITS_SET' orelse - Op =:= 'BITS_CLEAR' orelse - Op =:= 'BITS_TEST' orelse - Op =:= 'BITS_SUM' orelse - Op =:= 'BITS_OR' orelse - Op =:= 'BITS_AND' orelse - Op =:= 'BITS_DIFF' orelse - Op =:= 'SHA3' orelse - Op =:= 'SHA256' orelse - Op =:= 'BLAKE2B' orelse - Op =:= 'VERIFY_SIG' orelse - Op =:= 'VERIFY_SIG_SECP256K1' orelse - Op =:= 'ECVERIFY_SECP256K1' orelse - Op =:= 'ECRECOVER_SECP256K1' orelse - Op =:= 'CONTRACT_TO_ADDRESS' orelse - Op =:= 'ADDRESS_TO_CONTRACT' orelse - Op =:= 'AUTH_TX_HASH' orelse - Op =:= 'BYTES_TO_INT' orelse - Op =:= 'BYTES_TO_STR' orelse - Op =:= 'BYTES_CONCAT' orelse - Op =:= 'BYTES_SPLIT' orelse - Op =:= 'ORACLE_CHECK' orelse - Op =:= 'ORACLE_CHECK_QUERY' orelse - Op =:= 'IS_ORACLE' orelse - Op =:= 'IS_CONTRACT' orelse - Op =:= 'IS_PAYABLE' orelse - Op =:= 'CREATOR' orelse - false)). - -record(env, { contract, vars = [], locals = [], current_function, tailpos = true }). %% -- Debugging -------------------------------------------------------------- @@ -1339,10 +1263,11 @@ r_write_to_dead_var({i, Ann, I}, Code) -> r_write_to_dead_var(_, _) -> false. op_view(T) when is_tuple(T) -> - case tuple_to_list(T) of - [Op, R | As] when ?IsOp(Op) -> - {Op, R, As}; - _ -> false + [Op, R | As] = tuple_to_list(T), + case attributes(list_to_tuple([Op, dst | [src || _ <- As]])) of + #{ write := dst, read := [src] } -> {Op, R, As}; + #{ write := dst, read := [] } -> {Op, R, As}; + _ -> false end; op_view(_) -> false. From e5702c068cdd1d05ebd45b556222fdcfc3d4200d Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 10:15:14 +0100 Subject: [PATCH 12/35] Impure == writes to the chain Reading is ok --- src/aeso_fcode_to_fate.erl | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 330d8a4..7931822 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -801,19 +801,19 @@ attributes(I) -> {'BYTES_TO_STR', A, B} -> Pure(A, [B]); {'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]); {'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]); - {'ORACLE_CHECK', A, B, C, D} -> Impure(A, [B, C, D]); - {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]); - {'IS_ORACLE', A, B} -> Impure(A, [B]); - {'IS_CONTRACT', A, B} -> Impure(A, [B]); - {'IS_PAYABLE', A, B} -> Impure(A, [B]); + {'ORACLE_CHECK', A, B, C, D} -> Pure(A, [B, C, D]); + {'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Pure(A, [B, C, D, E]); + {'IS_ORACLE', A, B} -> Pure(A, [B]); + {'IS_CONTRACT', A, B} -> Pure(A, [B]); + {'IS_PAYABLE', A, B} -> Pure(A, [B]); {'CREATOR', A} -> Pure(A, []); {'ADDRESS', A} -> Pure(A, []); - {'BALANCE', A} -> Impure(A, []); - {'BALANCE_OTHER', A, B} -> Impure(A, [B]); + {'BALANCE', A} -> Pure(A, []); + {'BALANCE_OTHER', A, B} -> Pure(A, [B]); {'ORIGIN', A} -> Pure(A, []); {'CALLER', A} -> Pure(A, []); {'GASPRICE', A} -> Pure(A, []); - {'BLOCKHASH', A, B} -> Impure(A, [B]); + {'BLOCKHASH', A, B} -> Pure(A, [B]); {'BENEFICIARY', A} -> Pure(A, []); {'TIMESTAMP', A} -> Pure(A, []); {'GENERATION', A} -> Pure(A, []); @@ -832,10 +832,10 @@ attributes(I) -> {'ORACLE_QUERY', A, B, C, D, E, F, G, H} -> Impure(A, [B, C, D, E, F, G, H]); {'ORACLE_RESPOND', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]); {'ORACLE_EXTEND', A, B, C} -> Impure(none, [A, B, C]); - {'ORACLE_GET_ANSWER', A, B, C, D, E} -> Impure(A, [B, C, D, E]); - {'ORACLE_GET_QUESTION', A, B, C, D, E}-> Impure(A, [B, C, D, E]); - {'ORACLE_QUERY_FEE', A, B} -> Impure(A, [B]); - {'AENS_RESOLVE', A, B, C, D} -> Impure(A, [B, C, D]); + {'ORACLE_GET_ANSWER', A, B, C, D, E} -> Pure(A, [B, C, D, E]); + {'ORACLE_GET_QUESTION', A, B, C, D, E}-> Pure(A, [B, C, D, E]); + {'ORACLE_QUERY_FEE', A, B} -> Pure(A, [B]); + {'AENS_RESOLVE', A, B, C, D} -> Pure(A, [B, C, D]); {'AENS_PRECLAIM', A, B, C} -> Impure(none, [A, B, C]); {'AENS_CLAIM', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]); 'AENS_UPDATE' -> Impure(none, []);%% TODO From 6868bec3ed8e063597396c3467227e46b98bd169 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 10:15:40 +0100 Subject: [PATCH 13/35] Fix bug in dependency analysis of GAS --- src/aeso_fcode_to_fate.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 7931822..df1f0f1 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -820,7 +820,7 @@ attributes(I) -> {'MICROBLOCK', A} -> Pure(A, []); {'DIFFICULTY', A} -> Pure(A, []); {'GASLIMIT', A} -> Pure(A, []); - {'GAS', A} -> Impure(?a, A); + {'GAS', A} -> Pure(A, []); {'LOG0', A} -> Impure(none, [A]); {'LOG1', A, B} -> Impure(none, [A, B]); {'LOG2', A, B, C} -> Impure(none, [A, B, C]); From d4c5c610eef92626f91cd8269cb26fdb953d6f24 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 10:34:40 +0100 Subject: [PATCH 14/35] Don't include stack and immediates in liveness annotations --- src/aeso_fcode_to_fate.erl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index df1f0f1..678d1a8 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -632,7 +632,7 @@ pp_ann(Ind, [switch_body | Code]) -> [Ind, "SWITCH-BODY\n", pp_ann(Ind, Code)]; pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) -> Fmt = fun([]) -> "()"; - (Xs) -> string:join([lists:concat(["var", N]) || {var, N} <- Xs], " ") + (Xs) -> string:join([lists:flatten(pp_arg(X)) || X <- Xs], " ") end, Op = [Ind, pp_op(desugar_args(I))], Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []], @@ -684,7 +684,11 @@ ann_reads([{switch, Arg, Type, Alts, Def} | Code], Reads, Acc) -> Reads1 = ordsets:union([[Arg], Reads, ReadsDef | ReadsAlts]), ann_reads(Code, Reads1, [{switch, Arg, Type, Alts1, Def1} | Acc]); ann_reads([{i, _Ann, I} | Code], Reads, Acc) -> - #{ read := Rs, write := W, pure := Pure } = attributes(I), + #{ read := Rs0, write := W, pure := Pure } = attributes(I), + IsReg = fun({immediate, _}) -> false; + (?a) -> false; + (_) -> true end, + Rs = lists:filter(IsReg, Rs0), %% If we write it here it's not live in (unless we also read it) Reads1 = Reads -- [W], Reads2 = From 35b20800c95d4f41b3a1d06e500318a398694f46 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 11:09:18 +0100 Subject: [PATCH 15/35] Refactor argument inlining optimization --- src/aeso_fcode_to_fate.erl | 56 ++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 678d1a8..00a770e 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1200,38 +1200,42 @@ r_inline_store({i, _, {'STORE', R, R}}, Code) -> r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) -> %% Not when A is var unless updating the annotations properly. Inline = case A of - {arg, _} -> true; - ?i(_) -> true; - _ -> false + {arg, _} -> true; + ?i(_) -> true; + {store, _} -> true; + _ -> false end, - if Inline -> r_inline_store([I], R, A, Code); + if Inline -> r_inline_store([I], false, R, A, Code); true -> false end; r_inline_store(_, _) -> false. -r_inline_store(Acc, R, A, [switch_body | Code]) -> - r_inline_store([switch_body | Acc], R, A, Code); -r_inline_store(Acc, R, A, [{i, Ann, I} | Code]) -> - #{ write := W, pure := Pure } = attributes(I), +r_inline_store(Acc, Progress, R, A, [switch_body | Code]) -> + r_inline_store([switch_body | Acc], Progress, R, A, Code); +r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) -> + #{ write := W } = attributes(I), Inl = fun(X) when X == R -> A; (X) -> X end, - case not live_in(R, Ann) orelse not Pure orelse lists:member(W, [R, A]) of - true -> false; - false -> - case op_view(I) of - {Op, S, As} -> - case lists:member(R, As) of - true -> - Acc1 = [{i, Ann, from_op_view(Op, S, lists:map(Inl, As))} | Acc], - case r_inline_store(Acc1, R, A, Code) of - false -> {lists:reverse(Acc1), Code}; - {_, _} = Res -> Res - end; - false -> - r_inline_store([{i, Ann, I} | Acc], R, A, Code) - end; - _ -> r_inline_store([{i, Ann, I} | Acc], R, A, Code) + case live_in(R, Ann) of + false -> false; %% No more reads of R + true -> + {I1, Progress1} = + case op_view(I) of + {Op, S, As} -> + case lists:member(R, As) of + true -> {from_op_view(Op, S, lists:map(Inl, As)), true}; + false -> {I, Progress} + end; + _ -> {I, Progress} + end, + Acc1 = [{i, Ann, I1} | Acc], + %% Stop if write to R or A + case lists:member(W, [R, A]) of + true when Progress1 -> {lists:reverse(Acc1), Code}; + true -> false; + false -> r_inline_store(Acc1, Progress1, R, A, Code) end end; -r_inline_store(_Acc, _, _, _) -> false. +r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code}; +r_inline_store(_, false, _, _, _) -> false. %% Shortcut write followed by final read r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) -> @@ -1254,7 +1258,7 @@ r_one_shot_var(_, _) -> false. r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping r_write_to_dead_var({i, Ann, I}, Code) -> case op_view(I) of - {_Op, R = {var, _}, As} -> + {_Op, R, As} when R /= ?a -> case live_out(R, Ann) of false -> %% Subtle: we still have to pop the stack if any of the arguments From 0478df72fc7e66882420c3b7abfd2ee5c9f10033 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 13:01:36 +0100 Subject: [PATCH 16/35] Fix dependency analysis for loops --- src/aeso_fcode_to_fate.erl | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 00a770e..75697a9 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -683,6 +683,8 @@ ann_reads([{switch, Arg, Type, Alts, Def} | Code], Reads, Acc) -> {Def1, ReadsDef} = ann_reads(Def, Reads, []), Reads1 = ordsets:union([[Arg], Reads, ReadsDef | ReadsAlts]), ann_reads(Code, Reads1, [{switch, Arg, Type, Alts1, Def1} | Acc]); +ann_reads([{i, _Ann, loop} | Code], _Reads, Acc) -> + ann_reads_loop(10, Code, [], Acc); ann_reads([{i, _Ann, I} | Code], Reads, Acc) -> #{ read := Rs0, write := W, pure := Pure } = attributes(I), IsReg = fun({immediate, _}) -> false; @@ -705,6 +707,17 @@ ann_reads([{i, _Ann, I} | Code], Reads, Acc) -> ann_reads(Code, Reads2, [{i, Ann1, I} | Acc]); ann_reads([], Reads, Acc) -> {Acc, Reads}. +ann_reads_loop(Fuel, Code, Reads, Acc) -> + Ann1 = #{ live_in => Reads, live_out => [] }, + {Acc1, Reads1} = ann_reads(Code, Reads, [{i, Ann1, loop} | Acc]), + case Reads1 == Reads of + true -> {Acc1, Reads1}; + false when Fuel =< 0 -> + io:format("WARNING: Loop analysis fuel exhausted!\n"), + {Acc1, Reads1}; + false -> ann_reads_loop(Fuel - 1, Code, Reads1, Acc) + end. + %% Instruction attributes: reads, writes and purity (pure means no side-effects %% aside from the reads and writes). attributes(I) -> @@ -854,8 +867,12 @@ var_writes({i, _, I}) -> var_writes(I); var_writes(I) -> #{ write := W } = attributes(I), case W of - {var, _} -> [W]; - _ -> [] + {var, _} -> [W]; + {arg, _} -> [W]; + {store, _} -> [W]; + {stack, _} -> []; + none -> []; + pc -> [] end. -spec independent(sinstr_a(), sinstr_a()) -> boolean(). From 4976e0402e1a2550ade21ab130e74357741b43cd Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 11 Nov 2019 13:01:55 +0100 Subject: [PATCH 17/35] Don't crash constant propagation on ill-typed code --- src/aeso_fcode_to_fate.erl | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 75697a9..def38d8 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1132,20 +1132,21 @@ r_constant_propagation({i, Ann, I}, Code) -> end; r_constant_propagation(_, _) -> false. -eval_op('ADD', [X, Y]) -> X + Y; -eval_op('SUB', [X, Y]) -> X - Y; -eval_op('MUL', [X, Y]) -> X * Y; -eval_op('DIV', [X, Y]) when Y /= 0 -> X div Y; -eval_op('MOD', [X, Y]) when Y /= 0 -> X rem Y; -eval_op('POW', [_, _]) -> no_eval; -eval_op('LT', [X, Y]) -> X < Y; -eval_op('GT', [X, Y]) -> X > Y; -eval_op('EQ', [X, Y]) -> X =:= Y; -eval_op('ELT', [X, Y]) -> X =< Y; -eval_op('EGT', [X, Y]) -> X >= Y; -eval_op('NEQ', [X, Y]) -> X =/= Y; -eval_op('NOT', [X]) -> not X; -eval_op(_, _) -> no_eval. %% TODO: bits? +eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y; +eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y; +eval_op('MUL', [X, Y]) when is_integer(X), is_integer(Y) -> X * Y; +eval_op('DIV', [X, Y]) when is_integer(X), is_integer(Y), Y /= 0 -> X div Y; +eval_op('MOD', [X, Y]) when is_integer(X), is_integer(Y), Y /= 0 -> X rem Y; +eval_op('POW', [_, _]) -> no_eval; +eval_op('LT', [X, Y]) -> X < Y; +eval_op('GT', [X, Y]) -> X > Y; +eval_op('EQ', [X, Y]) -> X =:= Y; +eval_op('ELT', [X, Y]) -> X =< Y; +eval_op('EGT', [X, Y]) -> X >= Y; +eval_op('NEQ', [X, Y]) -> X =/= Y; +eval_op('NOT', [true]) -> false; +eval_op('NOT', [false]) -> true; +eval_op(_, _) -> no_eval. %% TODO: bits? %% Prune impossible branches from switches r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) -> From 2d7c860e3aedacdc917cb2721aa11e68f0036ed2 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 12 Nov 2019 09:29:46 +0100 Subject: [PATCH 18/35] Rewrite liveness analysis --- src/aeso_fcode_to_fate.erl | 106 +++++++++++++++---------------------- 1 file changed, 43 insertions(+), 63 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index def38d8..10be163 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -653,73 +653,52 @@ pp_arg(?a) -> "a". %% -- Analysis -- annotate_code(Code) -> - {WCode, _} = ann_writes(Code, ordsets:new(), []), - {RCode, _} = ann_reads(WCode, ordsets:new(), []), - RCode. + annotate_code(5, [], Code). -%% Reverses the code -ann_writes(missing, Writes, []) -> {missing, Writes}; -ann_writes([switch_body | Code], Writes, Acc) -> - ann_writes(Code, Writes, [switch_body | Acc]); -ann_writes([{switch, Arg, Type, Alts, Def} | Code], Writes, Acc) -> - {Alts1, WritesAlts} = lists:unzip([ ann_writes(Alt, Writes, []) || Alt <- Alts ]), - {Def1, WritesDef} = ann_writes(Def, Writes, []), - Writes1 = ordsets:union(Writes, ordsets:intersection([WritesDef | WritesAlts])), - ann_writes(Code, Writes1, [{switch, Arg, Type, Alts1, Def1} | Acc]); -ann_writes([I | Code], Writes, Acc) -> - Ws = [ W || W <- var_writes(I) ], - Writes1 = ordsets:union(Writes, Ws), - Ann = #{ writes_in => Writes, writes_out => Writes1 }, - ann_writes(Code, Writes1, [{i, Ann, I} | Acc]); -ann_writes([], Writes, Acc) -> - {Acc, Writes}. - -%% Takes reversed code and unreverses it. -ann_reads(missing, Reads, []) -> {missing, Reads}; -ann_reads([switch_body | Code], Reads, Acc) -> - ann_reads(Code, Reads, [switch_body | Acc]); -ann_reads([{switch, Arg, Type, Alts, Def} | Code], Reads, Acc) -> - {Alts1, ReadsAlts} = lists:unzip([ ann_reads(Alt, Reads, []) || Alt <- Alts ]), - {Def1, ReadsDef} = ann_reads(Def, Reads, []), - Reads1 = ordsets:union([[Arg], Reads, ReadsDef | ReadsAlts]), - ann_reads(Code, Reads1, [{switch, Arg, Type, Alts1, Def1} | Acc]); -ann_reads([{i, _Ann, loop} | Code], _Reads, Acc) -> - ann_reads_loop(10, Code, [], Acc); -ann_reads([{i, _Ann, I} | Code], Reads, Acc) -> - #{ read := Rs0, write := W, pure := Pure } = attributes(I), - IsReg = fun({immediate, _}) -> false; - (?a) -> false; - (_) -> true end, - Rs = lists:filter(IsReg, Rs0), - %% If we write it here it's not live in (unless we also read it) - Reads1 = Reads -- [W], - Reads2 = - case {W, Pure andalso not ordsets:is_element(W, Reads)} of - %% This is a little bit dangerous: if writing to a dead variable, we ignore - %% the reads. Relies on dead writes to be removed by the - %% optimisations below (r_write_to_dead_var). - {{var, _}, true} -> Reads1; - _ -> ordsets:union(Reads1, Rs) - end, - LiveIn = Reads2, % For well-formed code this should be a subset of WritesIn - LiveOut = Reads, % and this of WritesOut, - Ann1 = #{ live_in => LiveIn, live_out => LiveOut }, - ann_reads(Code, Reads2, [{i, Ann1, I} | Acc]); -ann_reads([], Reads, Acc) -> {Acc, Reads}. - -ann_reads_loop(Fuel, Code, Reads, Acc) -> - Ann1 = #{ live_in => Reads, live_out => [] }, - {Acc1, Reads1} = ann_reads(Code, Reads, [{i, Ann1, loop} | Acc]), - case Reads1 == Reads of - true -> {Acc1, Reads1}; +annotate_code(Fuel, LiveTop, Code) -> + {Code1, LiveIn} = ann_live(LiveTop, Code, []), + case LiveIn == LiveTop of + true -> Code1; false when Fuel =< 0 -> - io:format("WARNING: Loop analysis fuel exhausted!\n"), - {Acc1, Reads1}; - false -> ann_reads_loop(Fuel - 1, Code, Reads1, Acc) + aeso_errors:throw(aeso_code_errors:format(liveness_analysis_out_of_fuel)); + false -> annotate_code(Fuel - 1, LiveIn, Code) end. -%% Instruction attributes: reads, writes and purity (pure means no side-effects -%% aside from the reads and writes). +ann_live(_LiveTop, missing, _LiveOut) -> {missing, []}; +ann_live(_LiveTop, [], LiveOut) -> {[], LiveOut}; +ann_live(LiveTop, [I | Is], LiveOut) -> + {Is1, LiveMid} = ann_live(LiveTop, Is, LiveOut), + {I1, LiveIn} = ann_live1(LiveTop, I, LiveMid), + {[I1 | Is1], LiveIn}. + +ann_live1(_LiveTop, switch_body, LiveOut) -> + {switch_body, LiveOut}; +ann_live1(LiveTop, loop, _LiveOut) -> + Ann = #{ live_in => LiveTop, live_out => [] }, + {{i, Ann, loop}, LiveTop}; +ann_live1(LiveTop, {switch, Arg, Type, Alts, Def}, LiveOut) -> + Read = [Arg || is_reg(Arg)], + {Alts1, LiveAlts} = lists:unzip([ ann_live(LiveTop, Alt, LiveOut) || Alt <- Alts ]), + {Def1, LiveDef} = ann_live(LiveTop, Def, LiveOut), + LiveIn = ordsets:union([Read, LiveDef | LiveAlts]), + {{switch, Arg, Type, Alts1, Def1}, LiveIn}; +ann_live1(_LiveTop, I, LiveOut) -> + #{ read := Reads0, write := W } = attributes(I), + Reads = lists:filter(fun is_reg/1, Reads0), + %% If we write it here it's not live in (unless we also read it) + LiveIn = ordsets:union(LiveOut -- [W], Reads), + Ann = #{ live_in => LiveIn, live_out => LiveOut }, + {{i, Ann, I}, LiveIn}. + +is_reg(?a) -> false; +is_reg(none) -> false; +is_reg(pc) -> false; +is_reg({immediate, _}) -> false; +is_reg({arg, _}) -> true; +is_reg({store, _}) -> true; +is_reg({var, _}) -> true. + +%% Instruction attributes: reads, writes and purity (pure means no writing to the chain). attributes(I) -> Set = fun(L) when is_list(L) -> ordsets:from_list(L); (X) -> ordsets:from_list([X]) end, @@ -1196,6 +1175,7 @@ pick_branch(_Type, _V, _Alts) -> r_inline_switch_target(Store = {i, _, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) -> Switch = {switch, A, Type, Alts, Def}, case R of + A -> false; ?a -> {[Switch], Code}; {var, _} -> case lists:any(fun(Alt) -> live_in(R, Alt) end, [Def | Alts]) of From 505603ad71158343e66ff84f0966032ff1f6b752 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 12 Nov 2019 10:06:18 +0100 Subject: [PATCH 19/35] More optimizations for impure instructions --- src/aeso_fcode_to_fate.erl | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 10be163..5aefb05 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1255,8 +1255,9 @@ r_one_shot_var(_, _) -> false. %% Remove writes to dead variables r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping r_write_to_dead_var({i, Ann, I}, Code) -> + #{ pure := Pure } = attributes(I), case op_view(I) of - {_Op, R, As} when R /= ?a -> + {_Op, R, As} when R /= ?a, Pure -> case live_out(R, Ann) of false -> %% Subtle: we still have to pop the stack if any of the arguments @@ -1268,15 +1269,18 @@ r_write_to_dead_var({i, Ann, I}, Code) -> end; r_write_to_dead_var(_, _) -> false. +op_view({'ABORT', R}) -> {'ABORT', none, [R]}; op_view(T) when is_tuple(T) -> [Op, R | As] = tuple_to_list(T), + CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end, case attributes(list_to_tuple([Op, dst | [src || _ <- As]])) of - #{ write := dst, read := [src] } -> {Op, R, As}; - #{ write := dst, read := [] } -> {Op, R, As}; - _ -> false + #{ write := dst, read := Rs } -> CheckReads(Rs, {Op, R, As}); + #{ write := none, read := Rs } -> CheckReads(Rs, {Op, none, [R | As]}); + _ -> false end; op_view(_) -> false. +from_op_view(Op, none, As) -> list_to_tuple([Op | As]); from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]). %% Desugar and specialize and remove annotations From 4cc88be296ab1a0915ff7004db61f84387824675 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 12 Nov 2019 10:32:02 +0100 Subject: [PATCH 20/35] Desugar `STORE R a` to `POP R` --- src/aeso_fcode_to_fate.erl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 5aefb05..5d384f8 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1303,6 +1303,7 @@ desugar({'ADD', A, A, ?i(1)}) -> [aeb_fate_ops:inc(desugar_arg(A))]; desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()]; desugar({'SUB', A, A, ?i(1)}) -> [aeb_fate_ops:dec(desugar_arg(A))]; desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(desugar_arg(A))]; +desugar({'STORE', R, ?a}) -> [aeb_fate_ops:pop(desugar_arg(R))]; desugar({switch, Arg, Type, Alts, Def}) -> [{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}]; desugar(missing) -> missing; From bfcb9ab32480d38269b370b2688a1bbd059e638f Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 12 Nov 2019 13:32:19 +0100 Subject: [PATCH 21/35] Annotate switch bodies --- src/aeso_fcode_to_fate.erl | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 5d384f8..fb763a3 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -19,7 +19,6 @@ -type scode() :: [sinstr()]. -type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all - | switch_body | tuple(). %% FATE instruction -type arg() :: tuple(). %% Not exported: aeb_fate_ops:fate_arg(). @@ -27,7 +26,6 @@ %% Annotated scode -type scode_a() :: [sinstr_a()]. -type sinstr_a() :: {switch, arg(), stype(), [maybe_scode_a()], maybe_scode_a()} %% last arg is catch-all - | switch_body | {i, ann(), tuple()}. %% FATE instruction -type ann() :: #{ live_in := vars(), live_out := vars() }. @@ -628,8 +626,6 @@ pp_ann(Ind, [{switch, Arg, Type, Alts, Def} | Code]) -> || {Tag, Alt} <- lists:zip(Tags, Alts), Alt /= missing], [[Ind1, "_ =>\n", pp_ann(Ind2, Def)] || Def /= missing], pp_ann(Ind, Code)]; -pp_ann(Ind, [switch_body | Code]) -> - [Ind, "SWITCH-BODY\n", pp_ann(Ind, Code)]; pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) -> Fmt = fun([]) -> "()"; (Xs) -> string:join([lists:flatten(pp_arg(X)) || X <- Xs], " ") @@ -640,6 +636,7 @@ pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) -> pp_ann(Ind, Code)]; pp_ann(_, []) -> []. +pp_op(switch_body) -> "SWITCH-BODY"; pp_op(loop) -> "LOOP"; pp_op(I) -> aeb_fate_pp:format_op(I, #{}). @@ -672,7 +669,8 @@ ann_live(LiveTop, [I | Is], LiveOut) -> {[I1 | Is1], LiveIn}. ann_live1(_LiveTop, switch_body, LiveOut) -> - {switch_body, LiveOut}; + Ann = #{ live_in => LiveOut, live_out => LiveOut }, + {{i, Ann, switch_body}, LiveOut}; ann_live1(LiveTop, loop, _LiveOut) -> Ann = #{ live_in => LiveTop, live_out => [] }, {{i, Ann, loop}, LiveTop}; @@ -707,6 +705,7 @@ attributes(I) -> Impure = fun(W, R) -> Attr(W, R, false) end, case I of loop -> Impure(pc, []); + switch_body -> Pure(none, []); 'RETURN' -> Impure(pc, []); {'RETURNR', A} -> Impure(pc, A); {'CALL', A} -> Impure(?a, [A]); @@ -857,8 +856,6 @@ var_writes(I) -> -spec independent(sinstr_a(), sinstr_a()) -> boolean(). %% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging independent(_, {switch, _, _, _, _}) -> false; -%% independent(switch_body, _) -> true; -independent(_, switch_body) -> true; independent({i, _, I}, {i, _, J}) -> #{ write := WI, read := RI, pure := PureI } = attributes(I), #{ write := WJ, read := RJ, pure := PureJ } = attributes(J), @@ -880,8 +877,6 @@ merge_ann(#{ live_in := LiveIn }, #{ live_out := LiveOut }) -> #{ live_in => LiveIn, live_out => LiveOut }. %% Swap two instructions. Precondition: the instructions are independent/2. -swap_instrs(I, switch_body) -> {switch_body, I}; -%% swap_instrs(switch_body, I) -> {I, switch_body}; %% Commented due to Dialyzer whinging swap_instrs({i, #{ live_in := Live1 }, I}, {i, #{ live_in := Live2, live_out := Live3 }, J}) -> %% Since I and J are independent the J can't read or write anything in %% that I writes. @@ -897,7 +892,6 @@ live_in({store, _}, _) -> true; live_in(R, #{ live_in := LiveIn }) -> ordsets:is_element(R, LiveIn); live_in(R, {i, Ann, _}) -> live_in(R, Ann); live_in(R, [I = {i, _, _} | _]) -> live_in(R, I); -live_in(R, [switch_body | Code]) -> live_in(R, Code); live_in(R, [{switch, A, _, Alts, Def} | _]) -> R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]); live_in(_, missing) -> false; @@ -989,8 +983,8 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) -> true -> false end; r_push_consume(_, _) -> false. -inline_push(Ann, Arg, Stack, [switch_body | Code], Acc) -> - inline_push(Ann, Arg, Stack, Code, [switch_body | Acc]); +inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) -> + inline_push(Ann, Arg, Stack, Code, [AI | Acc]); inline_push(Ann1, Arg, Stack, [{i, Ann2, I} = AI | Code], Acc) -> case op_view(I) of {Op, R, As} -> @@ -1064,8 +1058,8 @@ r_swap_write(I = {i, _, _}, [J | Code]) -> end; r_swap_write(_, _) -> false. -r_swap_write(Pre, I, [switch_body | Code]) -> - r_swap_write([switch_body | Pre], I, Code); +r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) -> + r_swap_write([J | Pre], I, Code); r_swap_write(Pre, I, Code0 = [J | Code]) -> case apply_rules_once(merge_rules(), I, Code0) of {_Rule, New, Rest} -> @@ -1188,8 +1182,9 @@ r_inline_switch_target(Store = {i, _, {'STORE', R, A}}, [{switch, R, Type, Alts, r_inline_switch_target(_, _) -> false. %% Float switch-body to closest switch -r_float_switch_body(I = {i, _, _}, [switch_body | Code]) -> - {[], [switch_body, I | Code]}; +r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) -> + {J1, I1} = swap_instrs(I, J), + {[], [J1, I1 | Code]}; r_float_switch_body(_, _) -> false. %% Inline stores @@ -1207,8 +1202,8 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) -> true -> false end; r_inline_store(_, _) -> false. -r_inline_store(Acc, Progress, R, A, [switch_body | Code]) -> - r_inline_store([switch_body | Acc], Progress, R, A, Code); +r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) -> + r_inline_store([I | Acc], Progress, R, A, Code); r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) -> #{ write := W } = attributes(I), Inl = fun(X) when X == R -> A; (X) -> X end, @@ -1287,7 +1282,6 @@ from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]). -spec unannotate(scode_a()) -> scode(); (sinstr_a()) -> sinstr(); (missing) -> missing. -unannotate(switch_body) -> [switch_body]; unannotate({switch, Arg, Type, Alts, Def}) -> [{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}]; unannotate(missing) -> missing; From 03ad1ad1dd3defbf15e43c3eb711b1f0fc0a43e6 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Thu, 14 Nov 2019 14:06:43 +0100 Subject: [PATCH 22/35] Protect switch optimizations against ill-typed code --- src/aeso_fcode_to_fate.erl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index fb763a3..94b2c2f 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1127,7 +1127,7 @@ r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) -> false -> false; Alt -> {Alt, Code} end; -r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) -> +r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false -> Alts1 = [if V -> missing; true -> False end, if V -> True; true -> missing end], case Alts == Alts1 of @@ -1139,7 +1139,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, end end; r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}}, - [{switch, R, Type, Alts, missing} | Code]) -> + [{switch, R, Type = {variant, _}, Alts, missing} | Code]) -> case {R, lists:nth(Tag + 1, Alts)} of {_, missing} -> Alts1 = [missing || _ <- Alts], @@ -1156,7 +1156,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_ end; r_prune_impossible_branches(_, _) -> false. -pick_branch(boolean, V, [False, True]) -> +pick_branch(boolean, V, [False, True]) when V == true; V == false -> Alt = if V -> True; true -> False end, case Alt of missing -> false; From 1a628ab29f715ff672e5f2164ba7c6d11765a82d Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 15 Nov 2019 22:38:38 +0100 Subject: [PATCH 23/35] Fix bad annotations on switch-body --- src/aeso_fcode_to_fate.erl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 94b2c2f..48a326c 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -984,7 +984,8 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) -> r_push_consume(_, _) -> false. inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) -> - inline_push(Ann, Arg, Stack, Code, [AI | Acc]); + {AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI), + inline_push(Ann1, Arg, Stack, Code, [AI1 | Acc]); inline_push(Ann1, Arg, Stack, [{i, Ann2, I} = AI | Code], Acc) -> case op_view(I) of {Op, R, As} -> @@ -1059,7 +1060,8 @@ r_swap_write(I = {i, _, _}, [J | Code]) -> r_swap_write(_, _) -> false. r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) -> - r_swap_write([J | Pre], I, Code); + {J1, I1} = swap_instrs(I, J), + r_swap_write([J1 | Pre], I1, Code); r_swap_write(Pre, I, Code0 = [J | Code]) -> case apply_rules_once(merge_rules(), I, Code0) of {_Rule, New, Rest} -> From 0b474843f999a6fb82ebd34819c83f56039d2ada Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Sat, 16 Nov 2019 10:18:40 +0100 Subject: [PATCH 24/35] Protect against ill-typed code --- src/aeso_fcode_to_fate.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 48a326c..6eb0de9 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1141,7 +1141,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, end end; r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}}, - [{switch, R, Type = {variant, _}, Alts, missing} | Code]) -> + [{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) -> case {R, lists:nth(Tag + 1, Alts)} of {_, missing} -> Alts1 = [missing || _ <- Alts], From d3f5d7f5c5afd771fb1941b54f48749b318955dc Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 18 Nov 2019 12:09:10 +0100 Subject: [PATCH 25/35] Fix lost dependency when inlining switch target --- src/aeso_fcode_to_fate.erl | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 6eb0de9..ec16353 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1168,7 +1168,13 @@ pick_branch(_Type, _V, _Alts) -> false. %% STORE R A, SWITCH R --> SWITCH A -r_inline_switch_target(Store = {i, _, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) -> +r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) -> + Ann1 = + case is_reg(A) of + true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) }; + false -> Ann + end, + Store = {i, Ann1, {'STORE', R, A}}, Switch = {switch, A, Type, Alts, Def}, case R of A -> false; From 7d8a773d6ad24d3d1794a8d552e790140ee7b8ab Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 19 Nov 2019 11:24:54 +0100 Subject: [PATCH 26/35] Fix type specs --- src/aeso_fcode_to_fate.erl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index ec16353..d1b9226 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -19,7 +19,9 @@ -type scode() :: [sinstr()]. -type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all - | tuple(). %% FATE instruction + | switch_body + | loop + | tuple() | atom(). %% FATE instruction -type arg() :: tuple(). %% Not exported: aeb_fate_ops:fate_arg(). @@ -60,6 +62,10 @@ debug(Tag, Options, Fun) -> false -> ok end. +-dialyzer({nowarn_function, [code_error/1]}). +code_error(Err) -> + aeso_errors:throw(aeso_code_errors:format(Err)). + %% -- Main ------------------------------------------------------------------- %% @doc Main entry point. @@ -152,8 +158,6 @@ bind_local(Name, Env) -> notail(Env) -> Env#env{ tailpos = false }. -code_error(Err) -> error(Err). - lookup_var(#env{vars = Vars}, X) -> case lists:keyfind(X, 1, Vars) of {_, Var} -> Var; @@ -657,7 +661,7 @@ annotate_code(Fuel, LiveTop, Code) -> case LiveIn == LiveTop of true -> Code1; false when Fuel =< 0 -> - aeso_errors:throw(aeso_code_errors:format(liveness_analysis_out_of_fuel)); + code_error(liveness_analysis_out_of_fuel); false -> annotate_code(Fuel - 1, LiveIn, Code) end. @@ -919,7 +923,7 @@ simpl_top(I, Code, Options) -> simpl_top(?SIMPL_FUEL, I, Code, Options). simpl_top(0, I, Code, _Options) -> - error({out_of_fuel, I, Code}); + code_error({optimizer_out_of_fuel, I, Code}); simpl_top(Fuel, I, Code, Options) -> apply_rules(Fuel, rules(), I, Code, Options). From 7f7f53e044795523187882a2014ae4cee575074e Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 19 Nov 2019 13:10:56 +0100 Subject: [PATCH 27/35] Fix issue in basic block generation --- src/aeso_fcode_to_fate.erl | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index d1b9226..682e9e1 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1393,6 +1393,8 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code], _ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall) %% ^ fall-through to the outer catchall end, + %% If we don't generate a switch, we need to pop the argument if on the stack. + Pop = [{'POP', ?void} || Arg == ?a], {Blk1, Code1, AltBlks} = case Type of boolean -> @@ -1408,7 +1410,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code], _ -> FalseCode ++ [{jump, RestRef}] end, case lists:usort(Alts) == [missing] of - true -> {Blk#blk{code = [{jump, DefRef}]}, [], []}; + true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []}; false -> case Arg of ?i(false) -> {Blk#blk{code = ElseCode}, [], ThenBlk}; @@ -1418,12 +1420,18 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code], end; tuple -> [TCode] = Alts, - {Blk#blk{code = TCode ++ [{jump, RestRef}]}, [], []}; + case TCode of + missing -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []}; + _ -> {Blk#blk{code = Pop ++ TCode ++ [{jump, RestRef}]}, [], []} + end; {variant, [_]} -> %% [SINGLE_CON_SWITCH] Single constructor switches don't need a %% switch instruction. [AltCode] = Alts, - {Blk#blk{code = AltCode ++ [{jump, RestRef}]}, [], []}; + case AltCode of + missing -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []}; + _ -> {Blk#blk{code = Pop ++ AltCode ++ [{jump, RestRef}]}, [], []} + end; {variant, _Ar} -> MkBlk = fun(missing) -> {DefRef, []}; (ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef) From d0cfd9cbbe83a7edf07be97458bf5def8ccaf9d4 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 19 Nov 2019 13:11:06 +0100 Subject: [PATCH 28/35] Export to_basic_blocks for tests --- src/aeso_fcode_to_fate.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 682e9e1..c80c8dd 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -12,7 +12,7 @@ -export([compile/2, term_to_fate/1]). -ifdef(TEST). --export([optimize_fun/4]). +-export([optimize_fun/4, to_basic_blocks/1]). -endif. %% -- Preamble --------------------------------------------------------------- From 2be3c9194df96ddd8855783c195879c01d704ad3 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 19 Nov 2019 14:49:06 +0100 Subject: [PATCH 29/35] Optimize switches with a single successful branch Typical case: require(_, _) --- src/aeso_fcode_to_fate.erl | 71 +++++++++++++++++++++++++++++++++++--- 1 file changed, 67 insertions(+), 4 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index c80c8dd..e67effc 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -965,6 +965,7 @@ rules() -> ?RULE(r_swap_write), ?RULE(r_constant_propagation), ?RULE(r_prune_impossible_branches), + ?RULE(r_single_successful_branch), ?RULE(r_inline_store), ?RULE(r_float_switch_body) ]. @@ -1171,6 +1172,52 @@ pick_branch(boolean, V, [False, True]) when V == true; V == false -> pick_branch(_Type, _V, _Alts) -> false. +%% If there's a single branch that doesn't abort we can push the code for that +%% out of the switch. +r_single_successful_branch({switch, R, Type, Alts, Def}, Code) -> + case push_code_out_of_switch([Def | Alts]) of + {_, none} -> false; + {_, many} -> false; + {_, []} -> false; + {_, [{i, _, switch_body}]} -> false; + {[Def1 | Alts1], PushedOut} -> + {[{switch, R, Type, Alts1, Def1} | PushedOut], Code} + end; +r_single_successful_branch(_, _) -> false. + +push_code_out_of_switch([]) -> {[], none}; +push_code_out_of_switch([Alt | Alts]) -> + {Alt1, PushedAlt} = push_code_out_of_alt(Alt), + {Alts1, PushedAlts} = push_code_out_of_switch(Alts), + Pushed = + case {PushedAlt, PushedAlts} of + {none, _} -> PushedAlts; + {_, none} -> PushedAlt; + _ -> many + end, + {[Alt1 | Alts1], Pushed}. + +push_code_out_of_alt(missing) -> {missing, none}; +push_code_out_of_alt([Body = {i, _, switch_body} | Code]) -> + case does_abort(Code) of + true -> {[Body | Code], none}; + false -> {[Body], [Body | Code]} %% Duplicate the switch_body, in case we apply this in the middle of a switch + end; +push_code_out_of_alt([{switch, R, Type, Alts, Def}]) -> + {[Def1 | Alts1], Pushed} = push_code_out_of_switch([Def | Alts]), + {[{switch, R, Type, Alts1, Def1}], Pushed}; +push_code_out_of_alt(Code) -> + {Code, many}. %% Conservative + +does_abort([I | Code]) -> + does_abort(I) orelse does_abort(Code); +does_abort({i, _, {'ABORT', _}}) -> true; +does_abort({i, _, {'EXIT', _}}) -> true; +does_abort(missing) -> true; +does_abort({switch, _, _, Alts, Def}) -> + lists:all(fun does_abort/1, [Def | Alts]); +does_abort(_) -> false. + %% STORE R A, SWITCH R --> SWITCH A r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) -> Ann1 = @@ -1388,7 +1435,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code], {DefRef, DefBlk} = case Default of missing when Catchall == none -> - FreshBlk([aeb_fate_ops:exit(?i(<<"Incomplete patterns">>))], none); + FreshBlk([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none); missing -> {Catchall, []}; _ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall) %% ^ fall-through to the outer catchall @@ -1453,9 +1500,10 @@ optimize_blocks(Blocks) -> RBlockMap = maps:from_list(RBlocks), RBlocks1 = reorder_blocks(RBlocks, []), RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ], - RBlocks3 = remove_dead_blocks(RBlocks2), - RBlocks4 = [ {Ref, tweak_returns(Code)} || {Ref, Code} <- RBlocks3 ], - Rev(RBlocks4). + RBlocks3 = shortcut_jump_chains(RBlocks2), + RBlocks4 = remove_dead_blocks(RBlocks3), + RBlocks5 = [ {Ref, tweak_returns(Code)} || {Ref, Code} <- RBlocks4 ], + Rev(RBlocks5). %% Choose the next block based on the final jump. reorder_blocks([], Acc) -> @@ -1491,6 +1539,21 @@ inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref -> end; inline_block(_, _, Code) -> Code. +%% Shortcut jumps to blocks with a single jump +shortcut_jump_chains(RBlocks) -> + Subst = lists:foldl(fun({L1, [{jump, L2}]}, Sub) -> + Sub#{ L1 => maps:get(L2, Sub, L2) }; + (_, Sub) -> Sub end, #{}, RBlocks), + [ {Ref, update_labels(Subst, Code)} || {Ref, Code} <- RBlocks ]. + +update_labels(Sub, Ref) when is_reference(Ref) -> + maps:get(Ref, Sub, Ref); +update_labels(Sub, L) when is_list(L) -> + lists:map(fun(X) -> update_labels(Sub, X) end, L); +update_labels(Sub, T) when is_tuple(T) -> + list_to_tuple(update_labels(Sub, tuple_to_list(T))); +update_labels(_, X) -> X. + %% Remove unused blocks remove_dead_blocks(Blocks = [{Top, _} | _]) -> BlockMap = maps:from_list(Blocks), From 6380e04a97f9da5e32271650bc6fbe2cd2fd16b4 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Tue, 19 Nov 2019 16:39:01 +0100 Subject: [PATCH 30/35] Strip switches on variants with only catch-all --- src/aeso_fcode_to_fate.erl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index e67effc..3dd3252 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1480,11 +1480,15 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code], _ -> {Blk#blk{code = Pop ++ AltCode ++ [{jump, RestRef}]}, [], []} end; {variant, _Ar} -> - MkBlk = fun(missing) -> {DefRef, []}; - (ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef) - end, - {AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)), - {Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)} + case lists:usort(Alts) == [missing] of + true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []}; + false -> + MkBlk = fun(missing) -> {DefRef, []}; + (ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef) + end, + {AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)), + {Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)} + end end, Blk2 = Blk1#blk{catchall = DefRef}, %% Update catchall ref block(Blk2, Code1 ++ Acc, DefBlk ++ RestBlk ++ AltBlks ++ Blocks, BlockAcc); From bd64260e37d3e5fab2dc32aeac6767dbbf4be244 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 25 Nov 2019 10:42:37 +0100 Subject: [PATCH 31/35] Remove impossible case h/t dialyzer --- src/aeso_fcode_to_fate.erl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 3dd3252..49b199f 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -1178,7 +1178,6 @@ r_single_successful_branch({switch, R, Type, Alts, Def}, Code) -> case push_code_out_of_switch([Def | Alts]) of {_, none} -> false; {_, many} -> false; - {_, []} -> false; {_, [{i, _, switch_body}]} -> false; {[Def1 | Alts1], PushedOut} -> {[{switch, R, Type, Alts1, Def1} | PushedOut], Code} From 06e6138de1d34764d6bb5c5272ad25937a8dfc6a Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 25 Nov 2019 11:38:41 +0100 Subject: [PATCH 32/35] Merge release notes for 4.0.0 release candidates into 4.0.0 entry --- CHANGELOG.md | 48 ++++++++++++++---------------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f27a4a7..3730c99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,47 +13,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `Address.to_contract` - casts an address to a (any) contract type. - Pragma to check compiler version, e.g. `@compiler >= 4.0`. -### Changed -- Nice type error if contract function is called as from a namespace. -- Fail on function definitions in contracts other than the main contract. -- Bug fix in variable optimization - don't discard writes to the store/state. -### Removed - -## [4.0.0-rc5] - 2019-09-27 -### Added -### Changed -- Bug fixes in error reporting. -- Bug fix in variable liveness analysis for FATE. -### Removed - -## [4.0.0-rc4] - 2019-09-13 -### Added - Handle numeric escapes, i.e. `"\x19Ethereum Signed Message:\n"`, and similar strings. -### Changed -### Removed - -## [4.0.0-rc3] - 2019-09-12 -### Added - `Bytes.concat` and `Bytes.split` are added to be able to (de-)construct byte arrays. - `[a..b]` language construct, returning the list of numbers between `a` and `b` (inclusive). Returns the empty list if `a` > `b`. - [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md) - Checks that `init` is not called from other functions. -### Changed -- Error messages are changed into a uniform format, and more helpful - messages have been added. -- `Crypto.` and `String.` for byte arrays now only - hash the actual byte array - not the internal ABI format. -- More strict checks for polymorphic oracles and higher order oracles - and entrypoints. -- `AENS.claim` is updated with a `NameFee` field - to be able to do - name auctions within contracts. -- Fixed a bug in `Bytes.to_str` for AEVM. -### Removed - -## [4.0.0-rc1] - 2019-08-22 -### Added - FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts for. @@ -70,6 +36,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 that shall be able to receive funds should be marked as payable. `Address.is_payable(a)` can be used to check if an (contract) address is payable or not. ### Changed +- Nice type error if contract function is called as from a namespace. +- Fail on function definitions in contracts other than the main contract. +- Bug fix in variable optimization - don't discard writes to the store/state. +- Bug fixes in error reporting. +- Bug fix in variable liveness analysis for FATE. +- Error messages are changed into a uniform format, and more helpful + messages have been added. +- `Crypto.` and `String.` for byte arrays now only + hash the actual byte array - not the internal ABI format. +- More strict checks for polymorphic oracles and higher order oracles + and entrypoints. +- `AENS.claim` is updated with a `NameFee` field - to be able to do + name auctions within contracts. +- Fixed a bug in `Bytes.to_str` for AEVM. - New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and regular tuples are encoded by interspersing inner types with `*`, for instance `int * string`. Parens are not necessary. Note it only affects the types, values remain as their were before, From 025c83788605ea14ec64d0092472817ee56ce47c Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 25 Nov 2019 11:52:42 +0100 Subject: [PATCH 33/35] 4.1.0-rc1 change log --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3730c99..8220282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Removed +## [4.1.0-rc1] - 2019-11-25 +### Added +- Add functionality to check if bytecode from the chain was compiled from given + source code. In `aesophia_cli`: + ``` + aesophia_cli --validate cb_... MyContract.aes + ``` + In `aesophia_http`: `validate-byte-code` entrypoint. +- Support encoding and decoding bit fields in call arguments and results. +### Changed +- Fix bug with standard library includes in `aesophia_cli`. +- Various improvements to FATE code generator. +### Removed + ## [4.0.0] - 2019-10-11 ### Added - `Address.to_contract` - casts an address to a (any) contract type. From 8984ecc32d9c0b30252e74a0075fbf1eb4491fd8 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 25 Nov 2019 11:55:31 +0100 Subject: [PATCH 34/35] Bump version numbers --- rebar.config | 2 +- src/aesophia.app.src | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index e5ca552..194fe6f 100644 --- a/rebar.config +++ b/rebar.config @@ -15,7 +15,7 @@ {base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]} ]}. -{relx, [{release, {aesophia, "4.0.0"}, +{relx, [{release, {aesophia, "4.1.0-rc1"}, [aesophia, aebytecode, getopt]}, {dev_mode, true}, diff --git a/src/aesophia.app.src b/src/aesophia.app.src index d22866c..7efc2aa 100644 --- a/src/aesophia.app.src +++ b/src/aesophia.app.src @@ -1,6 +1,6 @@ {application, aesophia, [{description, "Contract Language for aeternity"}, - {vsn, "4.0.0"}, + {vsn, "4.1.0-rc1"}, {registered, []}, {applications, [kernel, From d4c9d369b1972628ffb4c765a86ddd9a70e54637 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 25 Nov 2019 12:07:05 +0100 Subject: [PATCH 35/35] Remove aesophia_cli and aesophia_http stuff from change log --- CHANGELOG.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8220282..7b4b022 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,15 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [4.1.0-rc1] - 2019-11-25 ### Added -- Add functionality to check if bytecode from the chain was compiled from given - source code. In `aesophia_cli`: - ``` - aesophia_cli --validate cb_... MyContract.aes - ``` - In `aesophia_http`: `validate-byte-code` entrypoint. - Support encoding and decoding bit fields in call arguments and results. ### Changed -- Fix bug with standard library includes in `aesophia_cli`. - Various improvements to FATE code generator. ### Removed