From 09638daa90e432292d42d195410459e276510aeb Mon Sep 17 00:00:00 2001 From: Gaith Hallak Date: Tue, 25 Oct 2022 09:42:02 +0300 Subject: [PATCH] Return mapping from variables to registers in fate compilation (#411) * Return mapping from variables to registers * Fix dialyzer issues * Record real names * Report saved fresh names as part of fcode env * Undo whitespace changes * Fix dialyzer warnings * Formatting fix * Use function names as strings * Manually handle making function names * Update CHANGELOG * Make variables registers optional * Update docs about the new flag * Remove empty saved_fresh_names map from fcode env --- CHANGELOG.md | 1 + docs/aeso_compiler.md | 2 + src/aeso_ast_infer_types.erl | 14 +++---- src/aeso_ast_to_fcode.erl | 57 ++++++++++++++++++-------- src/aeso_compiler.erl | 16 ++++++-- src/aeso_fcode_to_fate.erl | 77 +++++++++++++++++++++++++++--------- 6 files changed, 120 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab4867a..63e2043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 entrypoint spend(x : int) : int = x entrypoint f(c : Main) : int = c.spend(10) ``` +- Return a mapping from variables to FATE registers in the compilation output. ### Changed ### Removed ### Fixed diff --git a/docs/aeso_compiler.md b/docs/aeso_compiler.md index 4798ad4..61ee7aa 100644 --- a/docs/aeso_compiler.md +++ b/docs/aeso_compiler.md @@ -53,6 +53,8 @@ The **pp_** options all print to standard output the following: The option `include_child_contract_symbols` includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain. +The option `debug_info` includes information related to debugging in the compiler output. Currently this option only includes the mapping from variables to registers. + #### Options to control which compiler optimizations should run: By default all optimizations are turned on, to disable an optimization, it should be diff --git a/src/aeso_ast_infer_types.erl b/src/aeso_ast_infer_types.erl index 03cbe2f..2387779 100644 --- a/src/aeso_ast_infer_types.erl +++ b/src/aeso_ast_infer_types.erl @@ -1487,7 +1487,7 @@ check_reserved_entrypoints(Funs) -> check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type = {fun_t, _, _, _, _}}) -> Type1 = {fun_t, _, Named, Args, Ret} = check_type(Env, Type), TypeSig = {type_sig, Ann, none, Named, Args, Ret}, - register_implementation(Env, Name), + register_implementation(Name), {{Name, TypeSig}, {fun_decl, Ann, Id, Type1}}; check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) -> type_error({fundecl_must_have_funtype, Ann, Id, Type}), @@ -1495,11 +1495,11 @@ check_fundecl(Env, {fun_decl, Ann, Id = {id, _, Name}, Type}) -> %% Register the function FunName as implemented by deleting it from the functions %% to be implemented table if it is included there, or return true otherwise. --spec register_implementation(env(), FunName) -> true | no_return() when +-spec register_implementation(FunName) -> true | no_return() when FunName :: string(). -register_implementation(Env, Name) -> +register_implementation(Name) -> case ets_lookup(functions_to_implement, Name) of - [{Name, _, {fun_decl, _, _, DeclType}}] -> + [{Name, _, {fun_decl, _, _, _}}] -> ets_delete(functions_to_implement, Name); [] -> true; @@ -1509,9 +1509,9 @@ register_implementation(Env, Name) -> infer_nonrec(Env, LetFun) -> create_constraints(), - NewLetFun = {{FunName, FunSig}, _} = infer_letfun(Env, LetFun), + NewLetFun = {{FunName, _}, _} = infer_letfun(Env, LetFun), check_special_funs(Env, NewLetFun), - register_implementation(Env, FunName), + register_implementation(FunName), solve_then_destroy_and_report_unsolved_constraints(Env), Result = {TypeSig, _} = instantiate(NewLetFun), print_typesig(TypeSig), @@ -1541,7 +1541,7 @@ infer_letrec(Env, Defs) -> Inferred = [ begin Res = {{Name, TypeSig}, _} = infer_letfun(ExtendEnv, LF), - register_implementation(Env, Name), + register_implementation(Name), Got = proplists:get_value(Name, Funs), Expect = typesig_to_fun_t(TypeSig), unify(Env, Got, Expect, {check_typesig, Name, Got, Expect}), diff --git a/src/aeso_ast_to_fcode.erl b/src/aeso_ast_to_fcode.erl index 1e0138e..f3dd9cc 100644 --- a/src/aeso_ast_to_fcode.erl +++ b/src/aeso_ast_to_fcode.erl @@ -149,17 +149,18 @@ -type state_layout() :: {tuple, [state_layout()]} | {reg, state_reg()}. --type env() :: #{ type_env := type_env(), - fun_env := fun_env(), - con_env := con_env(), - child_con_env := child_con_env(), - event_type => aeso_syntax:typedef(), - builtins := builtins(), - options := [option()], - state_layout => state_layout(), - context => context(), - vars => [var_name()], - functions := #{ fun_name() => fun_def() } +-type env() :: #{ type_env := type_env(), + fun_env := fun_env(), + con_env := con_env(), + child_con_env := child_con_env(), + event_type => aeso_syntax:typedef(), + builtins := builtins(), + options := [option()], + state_layout => state_layout(), + context => context(), + vars => [var_name()], + functions := #{ fun_name() => fun_def() }, + saved_fresh_names => #{ var_name() => var_name() } }. -define(HASH_BYTES, 32). @@ -170,7 +171,7 @@ %% and produces Fate intermediate code. -spec ast_to_fcode(aeso_syntax:ast(), [option()]) -> {env(), fcode()}. ast_to_fcode(Code, Options) -> - init_fresh_names(), + init_fresh_names(Options), {Env1, FCode1} = to_fcode(init_env(Options), Code), FCode2 = optimize(FCode1, Options), Env2 = Env1#{ child_con_env := @@ -178,8 +179,13 @@ ast_to_fcode(Code, Options) -> fun (_, FC) -> optimize(FC, Options) end, maps:get(child_con_env, Env1) )}, - clear_fresh_names(), - {Env2, FCode2}. + Env3 = + case proplists:get_value(debug_info, Options, false) of + true -> Env2#{ saved_fresh_names => get(saved_fresh_names) }; + false -> Env2 + end, + clear_fresh_names(Options), + {Env3, FCode2}. optimize(FCode1, Options) -> Verbose = lists:member(pp_fcode, Options), @@ -1728,12 +1734,29 @@ resolve_fun(#{ fun_env := Funs, builtins := Builtin } = Env, Q) -> {{Fun, Ar}, _} -> {def_u, Fun, Ar} end. -init_fresh_names() -> +init_fresh_names(Options) -> + proplists:get_value(debug_info, Options, false) andalso init_saved_fresh_names(), put('%fresh', 0). -clear_fresh_names() -> +clear_fresh_names(Options) -> + proplists:get_value(debug_info, Options, false) andalso clear_saved_fresh_names(), erase('%fresh'). +init_saved_fresh_names() -> + put(saved_fresh_names, #{}). + +clear_saved_fresh_names() -> + erase(saved_fresh_names). + +-spec fresh_name_save(string()) -> var_name(). +fresh_name_save(Name) -> + Fresh = fresh_name(), + case get(saved_fresh_names) of + undefined -> ok; + Old -> put(saved_fresh_names, Old#{Fresh => Name}) + end, + Fresh. + -spec fresh_name() -> var_name(). fresh_name() -> fresh_name("%"). @@ -1862,7 +1885,7 @@ bottom_up(F, Env, Expr) -> (_) -> true end, case ShouldFreshen(X) of true -> - Z = fresh_name(), + Z = fresh_name_save(X), Env1 = Env#{ Z => E1 }, {'let', Z, E1, bottom_up(F, Env1, rename([{X, Z}], Body))}; false -> diff --git a/src/aeso_compiler.erl b/src/aeso_compiler.erl index d49c47d..31b4ef8 100644 --- a/src/aeso_compiler.erl +++ b/src/aeso_compiler.erl @@ -112,10 +112,12 @@ from_string(ContractString, Options) -> from_string1(ContractString, Options) -> #{ fcode := FCode - , fcode_env := #{child_con_env := ChildContracts} + , fcode_env := FCodeEnv , folded_typed_ast := FoldedTypedAst , warnings := Warnings } = string_to_code(ContractString, Options), - FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options), + #{ child_con_env := ChildContracts } = FCodeEnv, + SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}), + {FateCode, VarsRegs} = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options), pp_assembler(FateCode, Options), ByteCode = aeb_fate_code:serialize(FateCode, []), {ok, Version} = version(), @@ -128,7 +130,13 @@ from_string1(ContractString, Options) -> payable => maps:get(payable, FCode), warnings => Warnings }, - {ok, maybe_generate_aci(Res, FoldedTypedAst, Options)}. + ResDbg = Res#{variables_registers => VarsRegs}, + FinalRes = + case proplists:get_value(debug_info, Options, false) of + true -> ResDbg; + false -> Res + end, + {ok, maybe_generate_aci(FinalRes, FoldedTypedAst, Options)}. maybe_generate_aci(Result, FoldedTypedAst, Options) -> case proplists:get_value(aci, Options) of @@ -185,7 +193,7 @@ check_call1(ContractString0, FunName, Args, Options) -> #{fcode := OrgFcode , fcode_env := #{child_con_env := ChildContracts} , ast := Ast} = string_to_code(ContractString0, Options), - FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []), + {FateCode, _} = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []), %% collect all hashes and compute the first name without hash collision to SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)), CallName = first_none_match(?CALL_NAME, SymbolHashes, diff --git a/src/aeso_fcode_to_fate.erl b/src/aeso_fcode_to_fate.erl index cdf0c8f..ebc4ebf 100644 --- a/src/aeso_fcode_to_fate.erl +++ b/src/aeso_fcode_to_fate.erl @@ -9,7 +9,7 @@ %%%------------------------------------------------------------------- -module(aeso_fcode_to_fate). --export([compile/2, compile/3, term_to_fate/1, term_to_fate/2]). +-export([compile/3, compile/4, term_to_fate/1, term_to_fate/2]). -ifdef(TEST). -export([optimize_fun/4, to_basic_blocks/1]). @@ -45,7 +45,14 @@ -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 = [], + current_function, + tailpos = true, + child_contracts = #{}, + saved_fresh_names = #{}, + options = [] }). %% -- Debugging -------------------------------------------------------------- @@ -71,19 +78,27 @@ code_error(Err) -> %% -- Main ------------------------------------------------------------------- %% @doc Main entry point. -compile(FCode, Options) -> - compile(#{}, FCode, Options). -compile(ChildContracts, FCode, Options) -> +compile(FCode, SavedFreshNames, Options) -> + compile(#{}, FCode, SavedFreshNames, Options). +compile(ChildContracts, FCode, SavedFreshNames, Options) -> + try + compile1(ChildContracts, FCode, SavedFreshNames, Options) + after + put(variables_registers, undefined) + end. + +compile1(ChildContracts, FCode, SavedFreshNames, Options) -> #{ contract_name := ContractName, functions := Functions } = FCode, - SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options), + SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options), SFuns1 = optimize_scode(SFuns, Options), FateCode = to_basic_blocks(SFuns1), ?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]), - case proplists:get_value(include_child_contract_symbols, Options, false) of - false -> FateCode; - true -> add_child_symbols(ChildContracts, FateCode) - end. + FateCode1 = case proplists:get_value(include_child_contract_symbols, Options, false) of + false -> FateCode; + true -> add_child_symbols(ChildContracts, FateCode) + end, + {FateCode1, get_variables_registers()}. make_function_id(X) -> aeb_fate_code:symbol_identifier(make_function_name(X)). @@ -97,22 +112,43 @@ add_child_symbols(ChildContracts, FateCode) -> Symbols = maps:from_list([ {make_function_id(FName), make_function_name(FName)} || FName <- Funs ]), aeb_fate_code:update_symbols(FateCode, Symbols). -functions_to_scode(ChildContracts, ContractName, Functions, Options) -> +functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options) -> FunNames = maps:keys(Functions), maps:from_list( - [ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)} + [ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, SavedFreshNames, Options)} || {Name, #{args := Args, body := Body, attrs := Attrs, return := Type}} <- maps:to_list(Functions)]). -function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, Options) -> +function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, SavedFreshNames, Options) -> {ArgTypes, ResType1} = typesig_to_scode(Args, ResType), Attrs = Attrs0 -- [stateful], %% Only track private and payable from here. - Env = init_env(ChildContracts, ContractName, Functions, Name, Args, Options), + Env = init_env(ChildContracts, ContractName, Functions, Name, Args, SavedFreshNames, Options), + [ add_variables_register(Env, Arg, Register) || + proplists:get_value(debug_info, Options, false), + {Arg, Register} <- Env#env.vars ], SCode = to_scode(Env, Body), {Attrs, {ArgTypes, ResType1}, SCode}. +get_variables_registers() -> + case get(variables_registers) of + undefined -> #{}; + Vs -> Vs + end. + +add_variables_register(Env = #env{saved_fresh_names = SavedFreshNames}, Name, Register) -> + Olds = get_variables_registers(), + RealName = maps:get(Name, SavedFreshNames, Name), + FunName = + case Env#env.current_function of + event -> "Chain.event"; + {entrypoint, BinName} -> binary_to_list(BinName); + {local_fun, QualName} -> lists:last(QualName) + end, + New = {Env#env.contract, FunName, RealName}, + put(variables_registers, Olds#{New => Register}). + -define(tvars, '$tvars'). typesig_to_scode(Args, Res) -> @@ -157,19 +193,21 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts). %% -- Environment functions -- -init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) -> +init_env(ChildContracts, ContractName, FunNames, Name, Args, SavedFreshNames, Options) -> #env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ], contract = ContractName, child_contracts = ChildContracts, locals = FunNames, current_function = Name, options = Options, - tailpos = true }. + tailpos = true, + saved_fresh_names = SavedFreshNames }. next_var(#env{ vars = Vars }) -> 1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]). bind_var(Name, Var, Env = #env{ vars = Vars }) -> + proplists:get_value(debug_info, Env#env.options, false) andalso add_variables_register(Env, Name, Var), Env#env{ vars = [{Name, Var} | Vars] }. bind_local(Name, Env) -> @@ -193,9 +231,10 @@ serialize_contract_code(Env, C) -> end, case maps:get(C, Cache, none) of none -> - Options = Env#env.options, - FCode = maps:get(C, Env#env.child_contracts), - FateCode = compile(Env#env.child_contracts, FCode, Options), + Options = Env#env.options, + SavedFreshNames = Env#env.saved_fresh_names, + FCode = maps:get(C, Env#env.child_contracts), + {FateCode, _} = compile1(Env#env.child_contracts, FCode, SavedFreshNames, Options), ByteCode = aeb_fate_code:serialize(FateCode, []), {ok, Version} = aeso_compiler:version(), OriginalSourceCode = proplists:get_value(original_src, Options, ""),