From dfa286d43c7bf69c4f48a1da481e32cf9e05648f Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Mon, 4 Feb 2019 14:08:26 +0100 Subject: [PATCH] Deadcode elimination (icode post pass) --- src/aeso_ast_to_icode.erl | 39 ++++++++++++++++++++++++++++++++++- src/aeso_icode.hrl | 2 +- test/aeso_compiler_tests.erl | 15 ++++++++++++-- test/contracts/deadcode.aes | 21 +++++++++++++++++++ test/contracts/nodeadcode.aes | 21 +++++++++++++++++++ 5 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 test/contracts/deadcode.aes create mode 100644 test/contracts/nodeadcode.aes diff --git a/src/aeso_ast_to_icode.erl b/src/aeso_ast_to_icode.erl index 794cbd5..4713275 100644 --- a/src/aeso_ast_to_icode.erl +++ b/src/aeso_ast_to_icode.erl @@ -21,7 +21,8 @@ convert_typed(TypedTree, Options) -> {contract, _, {con, _, Con}, _} -> Con; _ -> gen_error(last_declaration_must_be_contract) end, - code(TypedTree, aeso_icode:set_name(Name, aeso_icode:new(Options))). + Icode = code(TypedTree, aeso_icode:set_name(Name, aeso_icode:new(Options))), + deadcode_elimination(Icode). code([{contract, _Attribs, Con, Code}|Rest], Icode) -> NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, Icode)), @@ -786,3 +787,39 @@ builtin_call(Builtin, Args) -> add_builtins(Icode = #{functions := Funs}) -> Builtins = aeso_builtins:used_builtins(Funs), Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- Builtins ] ++ Funs}. + + +%% ------------------------------------------------------------------- +%% Deadcode elimination +%% ------------------------------------------------------------------- + +deadcode_elimination(Icode = #{ functions := Funs }) -> + PublicNames = [ Name || {Name, Ann, _, _, _} <- Funs, not lists:member(private, Ann) ], + ArgsToPat = fun(Args) -> [ #var_ref{ name = X } || {X, _} <- Args ] end, + Defs = maps:from_list([ {Name, {binder, ArgsToPat(Args), Body}} || {Name, _, Args, Body, _} <- Funs ]), + UsedNames = chase_names(Defs, PublicNames, #{}), + UsedFuns = [ Def || Def = {Name, _, _, _, _} <- Funs, maps:is_key(Name, UsedNames) ], + Icode#{ functions := UsedFuns }. + +chase_names(_Defs, [], Used) -> Used; +chase_names(Defs, [X | Xs], Used) -> + %% can happen when compiling __call contracts + case maps:is_key(X, Used) orelse not maps:is_key(X, Defs) of + true -> chase_names(Defs, Xs, Used); %% already chased + false -> + Def = maps:get(X, Defs), + Vars = maps:keys(free_vars(Def)), + chase_names(Defs, Vars ++ Xs, Used#{ X => true }) + end. + +free_vars(#var_ref{ name = X }) -> #{ X => true }; +free_vars(#arg{ name = X }) -> #{ X => true }; +free_vars({binder, Pat, Body}) -> + maps:without(maps:keys(free_vars(Pat)), free_vars(Body)); +free_vars(#switch{ expr = E, cases = Cases }) -> + free_vars([E | [{binder, P, B} || {P, B} <- Cases]]); +free_vars(#lambda{ args = Xs, body = E }) -> + free_vars({binder, Xs, E}); +free_vars(T) when is_tuple(T) -> free_vars(tuple_to_list(T)); +free_vars([H | T]) -> maps:merge(free_vars(H), free_vars(T)); +free_vars(_) -> #{}. diff --git a/src/aeso_icode.hrl b/src/aeso_icode.hrl index 2fdabf5..56da80a 100644 --- a/src/aeso_icode.hrl +++ b/src/aeso_icode.hrl @@ -20,7 +20,7 @@ , args :: arg_list() , body :: expr()}). --record(var_ref, { name :: string() | {builtin, atom() | tuple()}}). +-record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}). -record(prim_call_contract, { gas :: expr() diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index 0880017..eccb6a0 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -26,7 +26,16 @@ simple_compile_test_() -> <<"Type errors\n",ErrorString/binary>> = compile(ContractName), check_errors(lists:sort(ExpectedErrors), ErrorString) end} || - {ContractName, ExpectedErrors} <- failing_contracts() ]. + {ContractName, ExpectedErrors} <- failing_contracts() ] ++ + [ {"Testing deadcode elimination", + fun() -> + #{ byte_code := NoDeadCode } = compile("nodeadcode"), + #{ byte_code := DeadCode } = compile("deadcode"), + SizeNoDeadCode = byte_size(NoDeadCode), + SizeDeadCode = byte_size(DeadCode), + ?assertMatch({_, _, true}, {SizeDeadCode, SizeNoDeadCode, SizeDeadCode + 40 < SizeNoDeadCode}), + ok + end} ]. check_errors(Expect, ErrorString) -> %% This removes the final single \n as well. @@ -64,7 +73,9 @@ compilable_contracts() -> "stack", "test", "builtin_bug", - "builtin_map_get_bug" + "builtin_map_get_bug", + "nodeadcode", + "deadcode" ]. %% Contracts that should produce type errors diff --git a/test/contracts/deadcode.aes b/test/contracts/deadcode.aes new file mode 100644 index 0000000..b96bf34 --- /dev/null +++ b/test/contracts/deadcode.aes @@ -0,0 +1,21 @@ + +namespace List = + + function map1(f : 'a => 'b, xs : list('a)) = + switch(xs) + [] => [] + x :: xs => f(x) :: map1(f, xs) + + function map2(f : 'a => 'b, xs : list('a)) = + switch(xs) + [] => [] + x :: xs => f(x) :: map2(f, xs) + +contract Deadcode = + + function inc1(xs : list(int)) : list(int) = + List.map1((x) => x + 1, xs) + + function inc2(xs : list(int)) : list(int) = + List.map1((x) => x + 1, xs) + diff --git a/test/contracts/nodeadcode.aes b/test/contracts/nodeadcode.aes new file mode 100644 index 0000000..90774c4 --- /dev/null +++ b/test/contracts/nodeadcode.aes @@ -0,0 +1,21 @@ + +namespace List = + + function map1(f : 'a => 'b, xs : list('a)) = + switch(xs) + [] => [] + x :: xs => f(x) :: map1(f, xs) + + function map2(f : 'a => 'b, xs : list('a)) = + switch(xs) + [] => [] + x :: xs => f(x) :: map2(f, xs) + +contract Deadcode = + + function inc1(xs : list(int)) : list(int) = + List.map1((x) => x + 1, xs) + + function inc2(xs : list(int)) : list(int) = + List.map2((x) => x + 1, xs) +