Add separate flags for each scode optimization (#410)

* Add separate flags for each scode optimization

* Add a list of available optimizations to docs

* Update CONTRIBUTING.md

* Update docs/aeso_compiler.md

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>

* Prefix rules functions with optimize_ instead of r_

Co-authored-by: Radosław Rowicki <35342116+radrow@users.noreply.github.com>
This commit is contained in:
Gaith Hallak 2022-10-07 12:09:53 +03:00 committed by GitHub
parent da92ddbd5d
commit 1c83287d45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 89 additions and 65 deletions

View File

@ -10,6 +10,8 @@ The following points should be considered before creating a new PR to the Sophia
- If a PR introduces a new feature that is relevant to the users of the language, the [Sophia Features Documentation](docs/sophia_features.md) should be updated to describe the new feature. - If a PR introduces a new feature that is relevant to the users of the language, the [Sophia Features Documentation](docs/sophia_features.md) should be updated to describe the new feature.
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the [Sophia Syntax Documentation](docs/sophia_syntax.md) should be updated to include the new syntax. - If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the [Sophia Syntax Documentation](docs/sophia_syntax.md) should be updated to include the new syntax.
- If a PR introduces a new library, the public interface of the new library should be fully documented in the [Sophia Standard Library Documentation](docs/sophia_stdlib.md). - If a PR introduces a new library, the public interface of the new library should be fully documented in the [Sophia Standard Library Documentation](docs/sophia_stdlib.md).
- If a PR introduces a new compiler option, the new option should be documented in the file
[aeso_compiler.md](docs/aeso_compiler.md).
### Tests ### Tests

View File

@ -51,6 +51,32 @@ The **pp_** options all print to standard output the following:
`pp_assembler` - print the generated assembler code `pp_assembler` - print the generated assembler code
#### Options to control which compiler optimizations should run:
By default all optimizations are turned on, to disable an optimization, it should be
explicitly set to false and passed as a compiler option.
List of optimizations:
- optimize_inliner
- optimize_inline_local_functions
- optimize_bind_subexpressions
- optimize_let_floating
- optimize_simplifier
- optimize_drop_unused_lets
- optimize_push_consume
- optimize_one_shot_var
- optimize_write_to_dead_var
- optimize_inline_switch_target
- optimize_swap_push
- optimize_swap_pop
- optimize_swap_write
- optimize_constant_propagation
- optimize_prune_impossible_branches
- optimize_single_successful_branch
- optimize_inline_store
- optimize_float_switch_bod
#### check_call(ContractString, Options) -> CheckRet #### check_call(ContractString, Options) -> CheckRet
Types Types

View File

@ -708,13 +708,8 @@ tuple(N) -> aeb_fate_ops:tuple(?a, N).
%% Optimize %% Optimize
optimize_scode(Funs, Options) -> optimize_scode(Funs, Options) ->
case proplists:get_value(optimize_scode, Options, true) of maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
true -> Funs).
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
Funs);
false ->
Funs
end.
flatten(missing) -> missing; flatten(missing) -> missing;
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)). flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
@ -1097,7 +1092,8 @@ simpl_top(I, Code, Options) ->
simpl_top(0, I, Code, _Options) -> simpl_top(0, I, Code, _Options) ->
code_error({optimizer_out_of_fuel, I, Code}); code_error({optimizer_out_of_fuel, I, Code});
simpl_top(Fuel, I, Code, Options) -> simpl_top(Fuel, I, Code, Options) ->
apply_rules(Fuel, rules(), I, Code, Options). Rules = [R || R = {Rule, _} <- rules(), proplists:get_value(Rule, Options, true)],
apply_rules(Fuel, Rules, 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, Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end,
@ -1124,29 +1120,29 @@ apply_rules_once([{RName, Rule} | Rules], I, Code) ->
-define(RULE(Name), {Name, fun Name/2}). -define(RULE(Name), {Name, fun Name/2}).
merge_rules() -> merge_rules() ->
[?RULE(r_push_consume), [?RULE(optimize_push_consume),
?RULE(r_one_shot_var), ?RULE(optimize_one_shot_var),
?RULE(r_write_to_dead_var), ?RULE(optimize_write_to_dead_var),
?RULE(r_inline_switch_target) ?RULE(optimize_inline_switch_target)
]. ].
rules() -> rules() ->
merge_rules() ++ merge_rules() ++
[?RULE(r_swap_push), [?RULE(optimize_swap_push),
?RULE(r_swap_pop), ?RULE(optimize_swap_pop),
?RULE(r_swap_write), ?RULE(optimize_swap_write),
?RULE(r_constant_propagation), ?RULE(optimize_constant_propagation),
?RULE(r_prune_impossible_branches), ?RULE(optimize_prune_impossible_branches),
?RULE(r_single_successful_branch), ?RULE(optimize_single_successful_branch),
?RULE(r_inline_store), ?RULE(optimize_inline_store),
?RULE(r_float_switch_body) ?RULE(optimize_float_switch_body)
]. ].
%% Removing pushes that are immediately consumed. %% Removing pushes that are immediately consumed.
r_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) -> optimize_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
inline_push(Ann1, A, 0, Code, []); inline_push(Ann1, A, 0, Code, []);
%% Writing directly to memory instead of going through the accumulator. %% Writing directly to memory instead of going through the accumulator.
r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) -> optimize_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
IsPush = IsPush =
case op_view(I) of case op_view(I) of
{_, ?a, _} -> true; {_, ?a, _} -> true;
@ -1158,7 +1154,7 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
end, end,
if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code}; if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code};
true -> false end; true -> false end;
r_push_consume(_, _) -> false. optimize_push_consume(_, _) -> false.
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) -> inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
{AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI), {AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI),
@ -1191,7 +1187,7 @@ split_stack_arg(N, [A | As], Acc) ->
split_stack_arg(N1, As, [A | Acc]). split_stack_arg(N1, As, [A | Acc]).
%% Move PUSHes past non-stack instructions. %% Move PUSHes past non-stack instructions.
r_swap_push(Push = {i, _, PushI}, [I | Code]) -> optimize_swap_push(Push = {i, _, PushI}, [I | Code]) ->
case op_view(PushI) of case op_view(PushI) of
{_, ?a, _} -> {_, ?a, _} ->
case independent(Push, I) of case independent(Push, I) of
@ -1202,10 +1198,10 @@ r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
end; end;
_ -> false _ -> false
end; end;
r_swap_push(_, _) -> false. optimize_swap_push(_, _) -> false.
%% Move non-stack instruction past POPs. %% Move non-stack instruction past POPs.
r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) -> optimize_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
case independent(IA, JA) of case independent(IA, JA) of
true -> true ->
case {op_view(I), op_view(J)} of case {op_view(I), op_view(J)} of
@ -1213,7 +1209,7 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
{_, false} -> false; {_, false} -> false;
{{_, IR, IAs}, {_, RJ, JAs}} -> {{_, IR, IAs}, {_, RJ, JAs}} ->
NonStackI = not lists:member(?a, [IR | IAs]), NonStackI = not lists:member(?a, [IR | IAs]),
%% RJ /= ?a to not conflict with r_swap_push %% RJ /= ?a to not conflict with optimize_swap_push
PopJ = RJ /= ?a andalso lists:member(?a, JAs), PopJ = RJ /= ?a andalso lists:member(?a, JAs),
case NonStackI andalso PopJ of case NonStackI andalso PopJ of
false -> false; false -> false;
@ -1224,22 +1220,22 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
end; end;
false -> false false -> false
end; end;
r_swap_pop(_, _) -> false. optimize_swap_pop(_, _) -> false.
%% Match up writes to variables with instructions further down. %% Match up writes to variables with instructions further down.
r_swap_write(I = {i, _, _}, [J | Code]) -> optimize_swap_write(I = {i, _, _}, [J | Code]) ->
case {var_writes(I), independent(I, J)} of case {var_writes(I), independent(I, J)} of
{[_], true} -> {[_], true} ->
{J1, I1} = swap_instrs(I, J), {J1, I1} = swap_instrs(I, J),
r_swap_write([J1], I1, Code); optimize_swap_write([J1], I1, Code);
_ -> false _ -> false
end; end;
r_swap_write(_, _) -> false. optimize_swap_write(_, _) -> false.
r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) -> optimize_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
{J1, I1} = swap_instrs(I, J), {J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code); optimize_swap_write([J1 | Pre], I1, Code);
r_swap_write(Pre, I, Code0 = [J | Code]) -> optimize_swap_write(Pre, I, Code0 = [J | Code]) ->
case apply_rules_once(merge_rules(), I, Code0) of case apply_rules_once(merge_rules(), I, Code0) of
{_Rule, New, Rest} -> {_Rule, New, Rest} ->
{lists:reverse(Pre) ++ New, Rest}; {lists:reverse(Pre) ++ New, Rest};
@ -1248,27 +1244,27 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
false -> false; false -> false;
true -> true ->
{J1, I1} = swap_instrs(I, J), {J1, I1} = swap_instrs(I, J),
r_swap_write([J1 | Pre], I1, Code) optimize_swap_write([J1 | Pre], I1, Code)
end end
end; end;
r_swap_write(_, _, _) -> false. optimize_swap_write(_, _, _) -> false.
%% Precompute instructions with known values %% Precompute instructions with known values
r_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) -> optimize_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
Store = {i, Ann, {'STORE', S, ?i(false)}}, Store = {i, Ann, {'STORE', S, ?i(false)}},
Cons1 = case R of Cons1 = case R of
?a -> {i, Ann1, {'CONS', ?void, X, Xs}}; ?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
_ -> Cons _ -> Cons
end, end,
{[Cons1, Store], Code}; {[Cons1, Store], Code};
r_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) -> optimize_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
Store = {i, Ann, {'STORE', S, ?i(true)}}, Store = {i, Ann, {'STORE', S, ?i(true)}},
Nil1 = case R of Nil1 = case R of
?a -> {i, Ann1, {'NIL', ?void}}; ?a -> {i, Ann1, {'NIL', ?void}};
_ -> Nil _ -> Nil
end, end,
{[Nil1, Store], Code}; {[Nil1, Store], Code};
r_constant_propagation({i, Ann, I}, Code) -> optimize_constant_propagation({i, Ann, I}, Code) ->
case op_view(I) of case op_view(I) of
false -> false; false -> false;
{Op, R, As} -> {Op, R, As} ->
@ -1282,7 +1278,7 @@ r_constant_propagation({i, Ann, I}, Code) ->
end end
end end
end; end;
r_constant_propagation(_, _) -> false. optimize_constant_propagation(_, _) -> false.
eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y; 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('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
@ -1301,12 +1297,12 @@ eval_op('NOT', [false]) -> true;
eval_op(_, _) -> no_eval. %% TODO: bits? eval_op(_, _) -> no_eval. %% TODO: bits?
%% Prune impossible branches from switches %% Prune impossible branches from switches
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) -> optimize_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
case pick_branch(Type, V, Alts) of case pick_branch(Type, V, Alts) of
false -> false; false -> false;
Alt -> {Alt, Code} Alt -> {Alt, Code}
end; end;
r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false -> optimize_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
Alts1 = [if V -> missing; true -> False end, Alts1 = [if V -> missing; true -> False end,
if V -> True; true -> missing end], if V -> True; true -> missing end],
case Alts == Alts1 of case Alts == Alts1 of
@ -1317,7 +1313,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
_ -> {[{switch, ?i(V), boolean, Alts1, Def}], Code} _ -> {[{switch, ?i(V), boolean, Alts1, Def}], Code}
end end
end; end;
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}}, optimize_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
[{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) -> [{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) ->
case {R, lists:nth(Tag + 1, Alts)} of case {R, lists:nth(Tag + 1, Alts)} of
{_, missing} -> {_, missing} ->
@ -1333,7 +1329,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
false -> {Alt, Code} false -> {Alt, Code}
end end
end; end;
r_prune_impossible_branches(_, _) -> false. optimize_prune_impossible_branches(_, _) -> false.
pick_branch(boolean, V, [False, True]) when V == true; V == false -> pick_branch(boolean, V, [False, True]) when V == true; V == false ->
Alt = if V -> True; true -> False end, Alt = if V -> True; true -> False end,
@ -1346,7 +1342,7 @@ pick_branch(_Type, _V, _Alts) ->
%% If there's a single branch that doesn't abort we can push the code for that %% If there's a single branch that doesn't abort we can push the code for that
%% out of the switch. %% out of the switch.
r_single_successful_branch({switch, R, Type, Alts, Def}, Code) -> optimize_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
case push_code_out_of_switch([Def | Alts]) of case push_code_out_of_switch([Def | Alts]) of
{_, none} -> false; {_, none} -> false;
{_, many} -> false; {_, many} -> false;
@ -1354,7 +1350,7 @@ r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
{[Def1 | Alts1], PushedOut} -> {[Def1 | Alts1], PushedOut} ->
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code} {[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
end; end;
r_single_successful_branch(_, _) -> false. optimize_single_successful_branch(_, _) -> false.
push_code_out_of_switch([]) -> {[], none}; push_code_out_of_switch([]) -> {[], none};
push_code_out_of_switch([Alt | Alts]) -> push_code_out_of_switch([Alt | Alts]) ->
@ -1390,7 +1386,7 @@ does_abort({switch, _, _, Alts, Def}) ->
does_abort(_) -> false. does_abort(_) -> false.
%% STORE R A, SWITCH R --> SWITCH A %% STORE R A, SWITCH R --> SWITCH A
r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) -> optimize_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
Ann1 = Ann1 =
case is_reg(A) of case is_reg(A) of
true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) }; true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) };
@ -1409,18 +1405,18 @@ r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def}
end; end;
_ -> false %% impossible _ -> false %% impossible
end; end;
r_inline_switch_target(_, _) -> false. optimize_inline_switch_target(_, _) -> false.
%% Float switch-body to closest switch %% Float switch-body to closest switch
r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) -> optimize_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
{J1, I1} = swap_instrs(I, J), {J1, I1} = swap_instrs(I, J),
{[], [J1, I1 | Code]}; {[], [J1, I1 | Code]};
r_float_switch_body(_, _) -> false. optimize_float_switch_body(_, _) -> false.
%% Inline stores %% Inline stores
r_inline_store({i, _, {'STORE', R, R}}, Code) -> optimize_inline_store({i, _, {'STORE', R, R}}, Code) ->
{[], Code}; {[], Code};
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) -> optimize_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
%% Not when A is var unless updating the annotations properly. %% Not when A is var unless updating the annotations properly.
Inline = case A of Inline = case A of
{arg, _} -> true; {arg, _} -> true;
@ -1428,13 +1424,13 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
{store, _} -> true; {store, _} -> true;
_ -> false _ -> false
end, end,
if Inline -> r_inline_store([I], false, R, A, Code); if Inline -> optimize_inline_store([I], false, R, A, Code);
true -> false end; true -> false end;
r_inline_store(_, _) -> false. optimize_inline_store(_, _) -> false.
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) -> optimize_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
r_inline_store([I | Acc], Progress, R, A, Code); optimize_inline_store([I | Acc], Progress, R, A, Code);
r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) -> optimize_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
#{ write := W } = attributes(I), #{ write := W } = attributes(I),
Inl = fun(X) when X == R -> A; (X) -> X end, Inl = fun(X) when X == R -> A; (X) -> X end,
case live_in(R, Ann) of case live_in(R, Ann) of
@ -1454,14 +1450,14 @@ r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
case lists:member(W, [R, A]) of case lists:member(W, [R, A]) of
true when Progress1 -> {lists:reverse(Acc1), Code}; true when Progress1 -> {lists:reverse(Acc1), Code};
true -> false; true -> false;
false -> r_inline_store(Acc1, Progress1, R, A, Code) false -> optimize_inline_store(Acc1, Progress1, R, A, Code)
end end
end; end;
r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code}; optimize_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
r_inline_store(_, false, _, _, _) -> false. optimize_inline_store(_, false, _, _, _) -> false.
%% Shortcut write followed by final read %% Shortcut write followed by final read
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) -> optimize_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
case op_view(I) of case op_view(I) of
{Op, R = {var, _}, As} -> {Op, R = {var, _}, As} ->
Copy = case J of Copy = case J of
@ -1475,11 +1471,11 @@ r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
end; end;
_ -> false _ -> false
end; end;
r_one_shot_var(_, _) -> false. optimize_one_shot_var(_, _) -> false.
%% Remove writes to dead variables %% Remove writes to dead variables
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping optimize_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
r_write_to_dead_var({i, Ann, I}, Code) -> optimize_write_to_dead_var({i, Ann, I}, Code) ->
#{ pure := Pure } = attributes(I), #{ pure := Pure } = attributes(I),
case op_view(I) of case op_view(I) of
{_Op, R, As} when R /= ?a, Pure -> {_Op, R, As} when R /= ?a, Pure ->
@ -1492,7 +1488,7 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
end; end;
_ -> false _ -> false
end; end;
r_write_to_dead_var(_, _) -> false. optimize_write_to_dead_var(_, _) -> false.
op_view({'ABORT', R}) -> {'ABORT', none, [R]}; op_view({'ABORT', R}) -> {'ABORT', none, [R]};
op_view({'EXIT', R}) -> {'EXIT', none, [R]}; op_view({'EXIT', R}) -> {'EXIT', none, [R]};