From a942561907c0d29d382bbd066d67e1c718451baa Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Thu, 22 Aug 2019 14:49:48 +0200 Subject: [PATCH 1/3] Improved optimizations of FATE code --- src/aeso_fcode_to_fate.erl | 77 +++++++++++++++++++++++++++++--------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 2c44090..0c1d7f1 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -936,11 +936,20 @@ simpl_s({switch, Arg, Type, Alts, Def}, Options) -> {switch, Arg, Type, [simplify(A, Options) || A <- Alts], simplify(Def, Options)}; simpl_s(I, _) -> I. -simpl_top(I, Code, Options) -> - apply_rules(rules(), I, Code, Options). +%% Safe-guard against loops in the rewriting. Shouldn't happen so throw an +%% error if we run out. +-define(SIMPL_FUEL, 5000). -apply_rules(Rules, I, Code, Options) -> - Cons = fun(X, Xs) -> simpl_top(X, Xs, Options) end, +simpl_top(I, Code, Options) -> + simpl_top(?SIMPL_FUEL, I, Code, Options). + +simpl_top(0, I, Code, _Options) -> + error({out_of_fuel, I, Code}); +simpl_top(Fuel, I, Code, Options) -> + apply_rules(Fuel, rules(), I, Code, Options). + +apply_rules(Fuel, Rules, I, Code, Options) -> + Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end, case apply_rules_once(Rules, I, Code) of false -> [I | Code]; {RName, New, Rest} -> @@ -968,6 +977,7 @@ merge_rules() -> rules() -> merge_rules() ++ [?RULE(r_swap_push), + ?RULE(r_swap_pop), ?RULE(r_swap_write), ?RULE(r_constant_propagation), ?RULE(r_prune_impossible_branches), @@ -1005,11 +1015,12 @@ inline_push(Ann1, Arg, Stack, [{i, Ann2, I} = AI | Code], Acc) -> {As0, As1} = split_stack_arg(Stack, As), Acc1 = [{i, merge_ann(Ann1, Ann2), from_op_view(Op, R, As0 ++ [Arg] ++ As1)} | Acc], {lists:reverse(Acc1), Code}; - false -> + false when Arg /= R -> {AI1, {i, Ann1b, _}} = swap_instrs({i, Ann1, {'STORE', ?a, Arg}}, AI), - inline_push(Ann1b, Arg, Stack + Produces - Consumes, Code, [AI1 | Acc]) + inline_push(Ann1b, Arg, Stack + Produces - Consumes, Code, [AI1 | Acc]); + false -> false end; - false -> false + _ -> false end; inline_push(_, _, _, _, _) -> false. @@ -1021,16 +1032,42 @@ split_stack_arg(N, [A | As], Acc) -> true -> N end, split_stack_arg(N1, As, [A | Acc]). -%% Move PUSH A past non-stack instructions. -r_swap_push(Push = {i, _, {'STORE', ?a, _}}, [I | Code]) -> - case independent(Push, I) of - true -> - {I1, Push1} = swap_instrs(Push, I), - {[I1, Push1], Code}; - false -> false +%% Move PUSHes past non-stack instructions. +r_swap_push(Push = {i, _, PushI}, [I | Code]) -> + case op_view(PushI) of + {_, ?a, _} -> + case independent(Push, I) of + true -> + {I1, Push1} = swap_instrs(Push, I), + {[I1, Push1], Code}; + false -> false + end; + _ -> false end; r_swap_push(_, _) -> false. +%% Move non-stack instruction past POPs. +r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) -> + case independent(IA, JA) of + true -> + case {op_view(I), op_view(J)} of + {false, _} -> false; + {_, false} -> false; + {{_, IR, IAs}, {_, RJ, JAs}} -> + NonStackI = not lists:member(?a, [IR | IAs]), + %% RJ /= ?a to not conflict with r_swap_push + PopJ = RJ /= ?a andalso lists:member(?a, JAs), + case NonStackI andalso PopJ of + false -> false; + true -> + {JA1, IA1} = swap_instrs(IA, JA), + {[JA1, IA1], Code} + end + end; + false -> false + end; +r_swap_pop(_, _) -> false. + %% Match up writes to variables with instructions further down. r_swap_write(I = {i, _, _}, [J | Code]) -> case {var_writes(I), independent(I, J)} of @@ -1166,6 +1203,8 @@ r_float_switch_body(I = {i, _, _}, [switch_body | Code]) -> r_float_switch_body(_, _) -> false. %% Inline stores +r_inline_store({i, _, {'STORE', R, R}}, Code) -> + {[], 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 @@ -1258,9 +1297,13 @@ unannotate(Code) when is_list(Code) -> unannotate({i, _Ann, I}) -> [I]. %% Desugar and specialize -desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_ops:inc()]; -desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()]; -desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(A)]; +desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_ops:inc()]; +desugar({'ADD', A, ?i(1), A}) -> [aeb_fate_ops:inc(A)]; +desugar({'ADD', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:inc()]; +desugar({'ADD', A, A, ?i(1)}) -> [aeb_fate_ops:inc(A)]; +desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()]; +desugar({'SUB', A, A, ?i(1)}) -> [aeb_fate_ops:dec(A)]; +desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(A)]; desugar({switch, Arg, Type, Alts, Def}) -> [{switch, Arg, Type, [desugar(A) || A <- Alts], desugar(Def)}]; desugar(missing) -> missing; From 20064b72fa5db0aecbfec8d8b05aea4f946ac726 Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Thu, 22 Aug 2019 14:50:15 +0200 Subject: [PATCH 2/3] Compile tail-calls to current function to jumps --- src/aeso_fcode_to_fate.erl | 39 +++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 0c1d7f1..4b79fa0 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -114,7 +114,7 @@ Op =:= 'CREATOR' orelse false)). --record(env, { contract, vars = [], locals = [], tailpos = true }). +-record(env, { contract, vars = [], locals = [], current_function, tailpos = true }). %% -- Debugging -------------------------------------------------------------- @@ -154,10 +154,10 @@ functions_to_scode(ContractName, Functions, Options) -> attrs := Attrs, return := Type}} <- maps:to_list(Functions)]). -function_to_scode(ContractName, Functions, _Name, Attrs0, Args, Body, ResType, _Options) -> +function_to_scode(ContractName, Functions, Name, Attrs0, Args, Body, ResType, _Options) -> {ArgTypes, ResType1} = typesig_to_scode(Args, ResType), Attrs = Attrs0 -- [stateful], %% Only track private and payable from here. - SCode = to_scode(init_env(ContractName, Functions, Args), Body), + SCode = to_scode(init_env(ContractName, Functions, Name, Args), Body), {Attrs, {ArgTypes, ResType1}, SCode}. -define(tvars, '$tvars'). @@ -199,10 +199,11 @@ type_to_scode({tvar, X}) -> %% -- Environment functions -- -init_env(ContractName, FunNames, Args) -> +init_env(ContractName, FunNames, Name, Args) -> #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ], contract = ContractName, locals = FunNames, + current_function = Name, tailpos = true }. next_var(#env{ vars = Vars }) -> @@ -305,6 +306,24 @@ to_scode(Env, {'let', X, Expr, Body}) -> aeb_fate_ops:store({var, I}, {stack, 0}), to_scode(Env1, Body) ]; +to_scode(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args}) -> + %% Tail-call to current function, f(e0..en). Compile to + %% [ let xi = ei ] + %% [ STORE argi xi ] + %% jump 0 + {Vars, Code, _Env} = + lists:foldl(fun(Arg, {Is, Acc, Env1}) -> + {I, Env2} = bind_local("_", Env1), + ArgCode = to_scode(notail(Env2), Arg), + Acc1 = [Acc, ArgCode, + aeb_fate_ops:store({var, I}, ?a)], + {[I | Is], Acc1, Env2} + end, {[], [], Env}, Args), + [ Code, + [ aeb_fate_ops:store({arg, I}, {var, J}) + || {I, J} <- lists:zip(lists:seq(0, length(Vars) - 1), + lists:reverse(Vars)) ], + loop ]; to_scode(Env, {def, Fun, Args}) -> FName = make_function_id(Fun), Lbl = aeb_fate_data:make_string(FName), @@ -662,12 +681,15 @@ pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) -> Fmt = fun([]) -> "()"; (Xs) -> string:join([lists:concat(["var", N]) || {var, N} <- Xs], " ") end, - Op = [Ind, aeb_fate_pp:format_op(I, #{})], + Op = [Ind, pp_op(I)], Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []], [io_lib:format("~-40s~s\n", [Op, Ann]), pp_ann(Ind, Code)]; pp_ann(_, []) -> []. +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]); @@ -734,6 +756,7 @@ attributes(I) -> Pure = fun(W, R) -> Attr(W, R, true) end, Impure = fun(W, R) -> Attr(W, R, false) end, case I of + loop -> Impure(pc, []); 'RETURN' -> Impure(pc, []); {'RETURNR', A} -> Impure(pc, A); {'CALL', _} -> Impure(?a, []); @@ -1503,7 +1526,8 @@ split_calls(Ref, [], Acc, Blocks) -> split_calls(Ref, [I | Code], Acc, Blocks) when element(1, I) == 'CALL'; element(1, I) == 'CALL_R'; element(1, I) == 'CALL_GR'; - element(1, I) == 'jumpif' -> + element(1, I) == 'jumpif'; + I == loop -> split_calls(make_ref(), Code, [], [{Ref, lists:reverse([I | Acc])} | Blocks]); split_calls(Ref, [{'ABORT', _} = I | _Code], Acc, Blocks) -> lists:reverse([{Ref, lists:reverse([I | Acc])} | Blocks]); @@ -1516,7 +1540,8 @@ split_calls(Ref, [I | Code], Acc, Blocks) -> set_labels(Labels, {Ref, Code}) when is_reference(Ref) -> {maps:get(Ref, Labels), [ set_labels(Labels, I) || I <- Code ]}; -set_labels(Labels, {jump, Ref}) -> aeb_fate_ops:jump(maps:get(Ref, Labels)); +set_labels(_Labels, loop) -> aeb_fate_ops:jump(0); +set_labels(Labels, {jump, Ref}) -> aeb_fate_ops:jump(maps:get(Ref, Labels)); set_labels(Labels, {jumpif, Arg, Ref}) -> aeb_fate_ops:jumpif(Arg, maps:get(Ref, Labels)); set_labels(Labels, {switch, Arg, Refs}) -> case [ maps:get(Ref, Labels) || Ref <- Refs ] of From 07cf1627032933e566ea6029877c07e5c7f5be1f Mon Sep 17 00:00:00 2001 From: Ulf Norell Date: Fri, 23 Aug 2019 10:07:43 +0200 Subject: [PATCH 3/3] Fix performance problem in FATE optimiser caused by debug printing --- src/aeso_fcode_to_fate.erl | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 4b79fa0..eadd9dd 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -118,9 +118,12 @@ %% -- Debugging -------------------------------------------------------------- -debug(Tag, Options, Fmt, Args) -> +is_debug(Tag, Options) -> Tags = proplists:get_value(debug, Options, []), - case Tags == all orelse lists:member(Tag, Tags) of + Tags == all orelse lists:member(Tag, Tags). + +debug(Tag, Options, Fmt, Args) -> + case is_debug(Tag, Options) of true -> io:format(Fmt, Args); false -> ok end. @@ -976,7 +979,12 @@ apply_rules(Fuel, Rules, I, Code, Options) -> case apply_rules_once(Rules, I, Code) of false -> [I | Code]; {RName, New, Rest} -> - debug(opt_rules, Options, " Applied ~p:\n~s ==>\n~s\n", [RName, pp_ann(" ", [I | Code]), pp_ann(" ", New ++ Rest)]), + 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)]); + false -> ok + end, lists:foldr(Cons, Rest, New) end. @@ -1556,3 +1564,11 @@ set_labels(_, I) -> I. with_ixs(Xs) -> lists:zip(lists:seq(0, length(Xs) - 1), Xs). +drop_common_suffix(Xs, Ys) -> + drop_common_suffix_r(lists:reverse(Xs), lists:reverse(Ys)). + +drop_common_suffix_r([X | Xs], [X | Ys]) -> + drop_common_suffix_r(Xs, Ys); +drop_common_suffix_r(Xs, Ys) -> + {lists:reverse(Xs), lists:reverse(Ys)}. +