Deadcode elimination (icode post pass)
This commit is contained in:
parent
478da2af33
commit
dfa286d43c
@ -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(_) -> #{}.
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
21
test/contracts/deadcode.aes
Normal file
21
test/contracts/deadcode.aes
Normal 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)
|
||||||
|
|
21
test/contracts/nodeadcode.aes
Normal file
21
test/contracts/nodeadcode.aes
Normal 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)
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user