Deadcode elimination (icode post pass)

This commit is contained in:
Ulf Norell 2019-02-04 14:08:26 +01:00
parent 478da2af33
commit dfa286d43c
5 changed files with 94 additions and 4 deletions

View File

@ -21,7 +21,8 @@ convert_typed(TypedTree, Options) ->
{contract, _, {con, _, Con}, _} -> Con; {contract, _, {con, _, Con}, _} -> Con;
_ -> gen_error(last_declaration_must_be_contract) _ -> gen_error(last_declaration_must_be_contract)
end, 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) -> code([{contract, _Attribs, Con, Code}|Rest], Icode) ->
NewIcode = contract_to_icode(Code, aeso_icode:set_namespace(Con, 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}) -> add_builtins(Icode = #{functions := Funs}) ->
Builtins = aeso_builtins:used_builtins(Funs), Builtins = aeso_builtins:used_builtins(Funs),
Icode#{functions := [ aeso_builtins:builtin_function(B) || B <- 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(_) -> #{}.

View File

@ -20,7 +20,7 @@
, args :: arg_list() , args :: arg_list()
, body :: expr()}). , body :: expr()}).
-record(var_ref, { name :: string() | {builtin, atom() | tuple()}}). -record(var_ref, { name :: string() | list(string()) | {builtin, atom() | tuple()}}).
-record(prim_call_contract, -record(prim_call_contract,
{ gas :: expr() { gas :: expr()

View File

@ -26,7 +26,16 @@ simple_compile_test_() ->
<<"Type errors\n",ErrorString/binary>> = compile(ContractName), <<"Type errors\n",ErrorString/binary>> = compile(ContractName),
check_errors(lists:sort(ExpectedErrors), ErrorString) check_errors(lists:sort(ExpectedErrors), ErrorString)
end} || 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) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well. %% This removes the final single \n as well.
@ -64,7 +73,9 @@ compilable_contracts() ->
"stack", "stack",
"test", "test",
"builtin_bug", "builtin_bug",
"builtin_map_get_bug" "builtin_map_get_bug",
"nodeadcode",
"deadcode"
]. ].
%% Contracts that should produce type errors %% Contracts that should produce type errors

View File

@ -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)

View File

@ -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)