From 62a0ff2e4c3f46d098e0c8e83d97b5ccc952b7d3 Mon Sep 17 00:00:00 2001 From: radrow Date: Sat, 2 Jul 2022 21:53:42 +0200 Subject: [PATCH] Add loop operator in fcode --- src/aeso_ast_to_fcode.erl | 45 +++++++++++++++++++++-- src/aeso_fcode_to_fate.erl | 65 ++++++++++++++++++++++++++------- test/aeso_compiler_tests.erl | 69 +----------------------------------- test/contracts/test.aes | 11 ++---- 4 files changed, 98 insertions(+), 92 deletions(-) diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index d0328c9..159a74b 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -75,6 +75,7 @@ | {switch, fsplit()} | {set_state, state_reg(), fexpr()} | {get_state, state_reg()} + | {loop, fexpr(), var_name(), fexpr()} | {continue, fexpr()} | {break, fexpr()} %% The following (unapplied top-level functions/builtins and %% lambdas) are generated by the fcode compiler, but translated %% to closures by the lambda lifter. @@ -653,9 +654,20 @@ expr_to_fcode(Env, _Type, {list, _, Es}) -> nil, Es); expr_to_fcode(Env, _Type, {app, _, {'..', _}, [A, B]}) -> - {def_u, FromTo, _} = resolve_fun(Env, ["ListInternal", "from_to"]), - {def, FromTo, [expr_to_fcode(Env, A), expr_to_fcode(Env, B)]}; - + AV = fresh_name(), + BV = fresh_name(), + WithA = fun(X) -> {'let', AV, expr_to_fcode(Env, A), X} end, + WithB = fun(X) -> {'let', BV, expr_to_fcode(Env, B), X} end, + St = fresh_name(), + It = fresh_name(), + Init = {tuple, [nil, {var, AV}]}, + WithA(WithB( + {loop, Init, St, + {'let', It, {proj, {var, St}, 2}, + make_if({op, '=<', [{var, It}, {var, BV}]}, + {continue, {tuple, [{op, '::', [{var, It}, {proj, {var, St}, 1}]}]}}, + {break, {proj, {var, St}, 2}} + )}})); expr_to_fcode(Env, _Type, {list_comp, _, Yield, []}) -> {op, '::', [expr_to_fcode(Env, Yield), nil]}; expr_to_fcode(Env, _Type, {list_comp, As, Yield, [{comprehension_bind, Pat = {typed, _, _, PatType}, BindExpr}|Rest]}) -> @@ -1338,6 +1350,9 @@ lambda_lift_expr(Layout, Expr) -> {proj, A, I} -> {proj, lambda_lift_expr(Layout, A), I}; {set_proj, A, I, B} -> {set_proj, lambda_lift_expr(Layout, A), I, lambda_lift_expr(Layout, B)}; {op, Op, As} -> {op, Op, lambda_lift_exprs(Layout, As)}; + {loop, Init, I, Body} -> {loop, lambda_lift_expr(Layout, Init), I, lambda_lift_expr(Layout, Body)}; + {break, E} -> {break, lambda_lift_expr(Layout, E)}; + {continue, E} -> {continue, lambda_lift_expr(Layout, E)}; {'let', X, A, B} -> {'let', X, lambda_lift_expr(Layout, A), lambda_lift_expr(Layout, B)}; {funcall, A, Bs} -> {funcall, lambda_lift_expr(Layout, A), lambda_lift_exprs(Layout, Bs)}; {set_state, R, A} -> {set_state, R, lambda_lift_expr(Layout, A)}; @@ -1653,6 +1668,9 @@ read_only({switch, Split}) -> read_only(Split); read_only({split, _, _, Cases}) -> read_only(Cases); read_only({nosplit, E}) -> read_only(E); read_only({'case', _, Split}) -> read_only(Split); +read_only({loop, Init, _, Body}) -> read_only(Init) andalso read_only(Body); +read_only({break, E}) -> read_only(E); +read_only({continue, E}) -> read_only(E); read_only({'let', _, A, B}) -> read_only([A, B]); read_only({funcall, _, _}) -> false; read_only({closure, _, _}) -> internal_error(no_closures_here); @@ -1850,6 +1868,9 @@ free_vars(Expr) -> {proj, A, _} -> free_vars(A); {set_proj, A, _, B} -> free_vars([A, B]); {op, _, As} -> free_vars(As); + {loop, Init, Var, Body} -> free_vars(Init) ++ (free_vars(Body) -- [Var]); + {break, E} -> free_vars(E); + {continue, E} -> free_vars(E); {'let', X, A, B} -> free_vars([A, {lam, [X], B}]); {funcall, A, Bs} -> free_vars([A | Bs]); {set_state, _, A} -> free_vars(A); @@ -1881,6 +1902,9 @@ used_defs(Expr) -> {proj, A, _} -> used_defs(A); {set_proj, A, _, B} -> used_defs([A, B]); {op, _, As} -> used_defs(As); + {loop, I, _, B} -> used_defs(I) ++ used_defs(B); + {break, E} -> used_defs(E); + {continue, E} -> used_defs(E); {'let', _, A, B} -> used_defs([A, B]); {funcall, A, Bs} -> used_defs([A | Bs]); {set_state, _, A} -> used_defs(A); @@ -1917,6 +1941,9 @@ bottom_up(F, Env, Expr) -> {get_state, _} -> Expr; {closure, F, CEnv} -> {closure, F, bottom_up(F, Env, CEnv)}; {switch, Split} -> {switch, bottom_up(F, Env, Split)}; + {loop, Init, Var, Body} -> {loop, bottom_up(F, Env, Init), Var, bottom_up(F, Env, Body)}; + {break, E} -> {break, bottom_up(F, Env, E)}; + {continue, E} -> {continue, bottom_up(F, Env, E)}; {lam, Xs, B} -> {lam, Xs, bottom_up(F, Env, B)}; {'let', X, E, Body} -> E1 = bottom_up(F, Env, E), @@ -1978,6 +2005,11 @@ rename(Ren, Expr) -> {lam, Xs, B} -> {Zs, Ren1} = rename_bindings(Ren, Xs), {lam, Zs, rename(Ren1, B)}; + {loop, Init, Var, Body} -> + {Z, Ren1} = rename_binding(Ren, Var), + {loop, rename(Ren, Init), Z, rename(Ren1, Body)}; + {break, E} -> {break, rename(Ren, E)}; + {continue, E} -> {continue, rename(Ren, E)}; {'let', X, E, Body} -> {Z, Ren1} = rename_binding(Ren, X), {'let', Z, rename(Ren, E), rename(Ren1, Body)} @@ -2169,6 +2201,13 @@ pp_fexpr({tuple, Es}) -> pp_parens(pp_par(pp_punctuate(pp_text(","), [pp_fexpr(E) || E <- Es]))); pp_fexpr({proj, E, I}) -> pp_beside([pp_fexpr(E), pp_text("."), pp_int(I)]); +pp_fexpr({loop, Init, Var, Body}) -> + pp_par( + [ pp_beside([pp_text("loop"), pp_fexpr(Init), pp_text("as"), pp_text(Var)]) + , pp_fexpr(Body) + ]); +pp_fexpr({break, E}) -> pp_beside([pp_text("break"), pp_fexpr(E)]); +pp_fexpr({continue, E}) -> pp_beside([pp_text("continue"), pp_fexpr(E)]); pp_fexpr({lam, Xs, A}) -> pp_par([pp_fexpr({tuple, [{var, X} || X <- Xs]}), pp_text("=>"), prettypr:nest(2, pp_fexpr(A))]); diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index 67a4b5c..23e8b31 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -45,7 +45,7 @@ -define(s(N), {store, N}). -define(void, {var, 9999}). --record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}). +-record(env, { contract, vars = [], locals = [], break_ref = none, cont_ref = none, loop_it = none, current_function, tailpos = true, child_contracts = #{}, options = []}). %% -- Debugging -------------------------------------------------------------- @@ -77,6 +77,7 @@ compile(ChildContracts, FCode, Options) -> SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options), SFuns1 = optimize_scode(SFuns, Options), FateCode = to_basic_blocks(SFuns1), + io:format("FINAL:\~p\n\n", [FateCode]), ?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), FateCode. @@ -159,6 +160,9 @@ init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) -> next_var(#env{ vars = Vars }) -> 1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]). +bind_loop(ContRef, BreakRef, It, Env) -> + Env#env{break_ref = BreakRef, cont_ref = ContRef, loop_it = It}. + bind_var(Name, Var, Env = #env{ vars = Vars }) -> Env#env{ vars = [{Name, Var} | Vars] }. @@ -368,7 +372,19 @@ to_scode1(Env, {set_state, Reg, Val}) -> to_scode1(Env, {closure, Fun, FVs}) -> to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]}); - +to_scode1(Env, {loop, Init, It, Expr}) -> + ContRef = make_ref(), + BreakRef = make_ref(), + InitS = to_scode(Env, Init) ++ [{jump, ContRef}], + {ItV, Env1} = bind_local(It, Env), + ExprS = to_scode(bind_loop(ContRef, BreakRef, ItV, Env1), Expr) ++ [{jumpif, ?a, ContRef}, {jump, BreakRef}], + [{loop, InitS, It, ExprS, ContRef, BreakRef}]; +to_scode1(Env = #env{cont_ref = ContRef, loop_it = It}, {continue, Expr}) -> + ExprS = to_scode1(Env, Expr), + ExprS ++ [{'POP', It}, push(?i(1))]; +to_scode1(Env = #env{break_ref = BreakRef}, {break, Expr}) -> + ExprS = to_scode1(Env, Expr), + ExprS ++ [push(?i(0))]; to_scode1(Env, {switch, Case}) -> split_to_scode(Env, Case). @@ -712,6 +728,8 @@ flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)). flatten_s({switch, Arg, Type, Alts, Catch}) -> {switch, Arg, Type, [flatten(Alt) || Alt <- Alts], flatten(Catch)}; +flatten_s({loop, Init, It, Body, BRef, CRef}) -> + {loop, flatten(Init), It, flatten(Body), BRef, CRef}; flatten_s(I) -> I. -define(MAX_SIMPL_ITERATIONS, 10). @@ -808,6 +826,11 @@ ann_live1(LiveTop, {switch, Arg, Type, Alts, Def}, LiveOut) -> {Def1, LiveDef} = ann_live(LiveTop, Def, LiveOut), LiveIn = ordsets:union([Read, LiveDef | LiveAlts]), {{switch, Arg, Type, Alts1, Def1}, LiveIn}; +ann_live1(LiveTop, {loop, Init, It, Body, BRef, CRef}, LiveOut) -> + {Init1, LiveInit} = ann_live(LiveTop, Init, LiveOut), + {Body1, LiveBody} = ann_live(LiveTop, Body, LiveOut), + LiveIn = ordsets:union([It, LiveInit, LiveBody]), % TODO not sure about this + {{loop, Init1, It, Body1, BRef, CRef}, LiveIn}; ann_live1(_LiveTop, I, LiveOut) -> #{ read := Reads0, write := W } = attributes(I), Reads = lists:filter(fun is_reg/1, Reads0), @@ -843,7 +866,9 @@ attributes(I) -> {'CALL_T', A} -> Impure(pc, [A]); {'CALL_VALUE', A} -> Pure(A, []); {'JUMP', _} -> Impure(pc, []); + {jump, _} -> Impure(pc, []); {'JUMPIF', A, _} -> Impure(pc, A); + {jumpif, A, _} -> Impure(pc, A); {'SWITCH_V2', A, _, _} -> Impure(pc, A); {'SWITCH_V3', A, _, _, _} -> Impure(pc, A); {'SWITCH_VN', A, _} -> Impure(pc, A); @@ -1023,6 +1048,7 @@ var_writes(I) -> -spec independent(sinstr_a(), sinstr_a()) -> boolean(). %% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging independent(_, {switch, _, _, _, _}) -> false; +independent(_, {loop, _, _, _, _, _}) -> false; independent({i, _, I}, {i, _, J}) -> #{ write := WI, read := RI, pure := PureI } = attributes(I), #{ write := WJ, read := RJ, pure := PureJ } = attributes(J), @@ -1505,6 +1531,8 @@ from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]). (missing) -> missing. unannotate({switch, Arg, Type, Alts, Def}) -> [{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}]; +unannotate({loop, Init, It, Body, BRef, CRef}) -> + [{loop, unannotate(Init), It, unannotate(Body), BRef, CRef}]; unannotate(missing) -> missing; unannotate(Code) when is_list(Code) -> lists:flatmap(fun unannotate/1, Code); @@ -1547,6 +1575,7 @@ to_basic_blocks([], Acc) -> Acc. bb(_Name, Code) -> + io:format("FUN: ~p\nCODE:\n~p\n\n", [_Name, Code]), Blocks0 = blocks(Code), Blocks1 = optimize_blocks(Blocks0), Blocks = lists:flatmap(fun split_calls/1, Blocks1), @@ -1567,7 +1596,7 @@ bb(_Name, Code) -> -type bb() :: {bbref(), bcode()}. -type bcode() :: [binstr()]. -type binstr() :: {jump, bbref()} - | {jumpif, bbref()} + | {jumpif, term(), bbref()} | tuple(). %% FATE instruction -spec blocks(scode()) -> [bb()]. @@ -1581,27 +1610,37 @@ blocks([], Acc) -> blocks([Blk | Blocks], Acc) -> block(Blk, [], Blocks, Acc). +fresh_block(C, Ca) -> + R = make_ref(), + {R, [#blk{ref = R, code = C, catchall = Ca}]}. + -spec block(#blk{}, bcode(), [#blk{}], [bb()]) -> [bb()]. block(#blk{ref = Ref, code = []}, CodeAcc, Blocks, BlockAcc) -> blocks(Blocks, [{Ref, lists:reverse(CodeAcc)} | BlockAcc]); +block(Blk = #blk{code = [{loop, Init, _, Expr, ContRef, BreakRef} | Code], catchall = Catchall}, Acc, Blocks, BlockAcc) -> + LoopBlock = #blk{ref = ContRef, code = Expr, catchall = BreakRef}, + BreakBlock = #blk{ref = BreakRef, code = Code, catchall = Catchall}, + + io:format("INIT: ~p\n\nLOOP: ~p\n\nBREAK: ~p\n\n", [Init, Expr, Code]), + + block(Blk#blk{code = Init}, Acc, [LoopBlock, BreakBlock | Blocks], BlockAcc); block(Blk = #blk{code = [switch_body | Code]}, Acc, Blocks, BlockAcc) -> %% Reached the body of a switch. Clear catchall ref. block(Blk#blk{code = Code, catchall = none}, Acc, Blocks, BlockAcc); block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code], catchall = Catchall}, Acc, Blocks, BlockAcc) -> - FreshBlk = fun(C, Ca) -> - R = make_ref(), - {R, [#blk{ref = R, code = C, catchall = Ca}]} - end, - {RestRef, RestBlk} = FreshBlk(Code, Catchall), + {RestRef, RestBlk} = fresh_block(Code, Catchall), {DefRef, DefBlk} = case Default of missing when Catchall == none -> - FreshBlk([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none); + fresh_block([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none); missing -> {Catchall, []}; - _ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall) + _ -> fresh_block(Default ++ [{jump, RestRef}], Catchall) %% ^ fall-through to the outer catchall end, + + io:format("RestRef: ~p, DefRef: ~p\n\n", [RestRef, DefRef]), + %% If we don't generate a switch, we need to pop the argument if on the stack. Pop = [{'POP', ?void} || Arg == ?a], {Blk1, Code1, AltBlks} = @@ -1611,7 +1650,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code], {ThenRef, ThenBlk} = case TrueCode of missing -> {DefRef, []}; - _ -> FreshBlk(TrueCode ++ [{jump, RestRef}], DefRef) + _ -> fresh_block(TrueCode ++ [{jump, RestRef}], DefRef) end, ElseCode = case FalseCode of @@ -1646,7 +1685,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code], true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []}; false -> MkBlk = fun(missing) -> {DefRef, []}; - (ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef) + (ACode) -> fresh_block(ACode ++ [{jump, RestRef}], DefRef) end, {AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)), {Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)} @@ -1664,6 +1703,7 @@ optimize_blocks(Blocks) -> Rev = fun(Bs) -> [ {Ref, lists:reverse(Code)} || {Ref, Code} <- Bs ] end, RBlocks = Rev(Blocks), RBlockMap = maps:from_list(RBlocks), + io:format("REORDERING ~p\n\n", [RBlocks]), RBlocks1 = reorder_blocks(RBlocks, []), RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ], RBlocks3 = shortcut_jump_chains(RBlocks2), @@ -1686,6 +1726,7 @@ reorder_blocks(Ref, Code, Blocks, Acc) -> [{'EXIT', _}|_] -> reorder_blocks(Blocks, Acc1); [{'ABORT', _}|_] -> reorder_blocks(Blocks, Acc1); [{switch, _, _}|_] -> reorder_blocks(Blocks, Acc1); + [{jumpif, _, _}|_] -> reorder_blocks(Blocks, Acc1); [{jump, L}|_] -> NotL = fun({L1, _}) -> L1 /= L end, case lists:splitwith(NotL, Blocks) of diff --git a/test/aeso_compiler_tests.erl b/test/aeso_compiler_tests.erl index fe53c95..379daad 100644 --- a/test/aeso_compiler_tests.erl +++ b/test/aeso_compiler_tests.erl @@ -140,74 +140,7 @@ compile(Name, Options) -> %% The currently compilable contracts. compilable_contracts() -> - ["complex_types", - "counter", - "dutch_auction", - "environment", - "factorial", - "functions", - "fundme", - "identity", - "maps", - "oracles", - "remote_call", - "remote_call_ambiguous_record", - "simple", - "simple_storage", - "spend_test", - "stack", - "test", - "builtin_bug", - "builtin_map_get_bug", - "lc_record_bug", - "nodeadcode", - "deadcode", - "variant_types", - "state_handling", - "events", - "include", - "basic_auth", - "basic_auth_tx", - "bitcoin_auth", - "address_literals", - "bytes_equality", - "address_chain", - "namespace_bug", - "bytes_to_x", - "bytes_concat", - "aens", - "aens_update", - "tuple_match", - "cyclic_include", - "stdlib_include", - "double_include", - "manual_stdlib_include", - "list_comp", - "payable", - "unapplied_builtins", - "underscore_number_literals", - "pairing_crypto", - "qualified_constructor", - "let_patterns", - "lhs_matching", - "more_strings", - "protected_call", - "hermetization_turnoff", - "multiple_contracts", - "clone", - "clone_simple", - "create", - "child_contract_init_bug", - "using_namespace", - "assign_patterns", - "patterns_guards", - "pipe_operator", - "polymorphism_contract_implements_interface", - "polymorphism_contract_multi_interface", - "polymorphism_contract_interface_extends_interface", - "polymorphism_contract_interface_extensions", - "polymorphism_contract_interface_same_decl_multi_interface", - "polymorphism_contract_interface_same_name_same_type", + [ "test" % Custom general-purpose test file. Keep it last on the list. ]. diff --git a/test/contracts/test.aes b/test/contracts/test.aes index d978179..5a90d06 100644 --- a/test/contracts/test.aes +++ b/test/contracts/test.aes @@ -1,12 +1,5 @@ // This is a custom test file if you need to run a compiler without // changing aeso_compiler_tests.erl -include "List.aes" - -contract IntegerHolder = - type state = int - entrypoint init(x) = x - entrypoint get() = state - -main contract Test = - stateful entrypoint f(c) = Chain.clone(ref=c, 123) +contract C = + entrypoint test() = [1..5] \ No newline at end of file