commit
eec70f03a5
55
CHANGELOG.md
55
CHANGELOG.md
@ -9,51 +9,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Changed
|
### Changed
|
||||||
### Removed
|
### Removed
|
||||||
|
|
||||||
|
## [4.1.0-rc1] - 2019-11-25
|
||||||
|
### Added
|
||||||
|
- Support encoding and decoding bit fields in call arguments and results.
|
||||||
|
### Changed
|
||||||
|
- Various improvements to FATE code generator.
|
||||||
|
### Removed
|
||||||
|
|
||||||
## [4.0.0] - 2019-10-11
|
## [4.0.0] - 2019-10-11
|
||||||
### Added
|
### Added
|
||||||
- `Address.to_contract` - casts an address to a (any) contract type.
|
- `Address.to_contract` - casts an address to a (any) contract type.
|
||||||
- Pragma to check compiler version, e.g. `@compiler >= 4.0`.
|
- Pragma to check compiler version, e.g. `@compiler >= 4.0`.
|
||||||
### Changed
|
|
||||||
- Nice type error if contract function is called as from a namespace.
|
|
||||||
- Fail on function definitions in contracts other than the main contract.
|
|
||||||
- Bug fix in variable optimization - don't discard writes to the store/state.
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
## [4.0.0-rc5] - 2019-09-27
|
|
||||||
### Added
|
|
||||||
### Changed
|
|
||||||
- Bug fixes in error reporting.
|
|
||||||
- Bug fix in variable liveness analysis for FATE.
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
## [4.0.0-rc4] - 2019-09-13
|
|
||||||
### Added
|
|
||||||
- Handle numeric escapes, i.e. `"\x19Ethereum Signed Message:\n"`, and similar strings.
|
- Handle numeric escapes, i.e. `"\x19Ethereum Signed Message:\n"`, and similar strings.
|
||||||
### Changed
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
## [4.0.0-rc3] - 2019-09-12
|
|
||||||
### Added
|
|
||||||
- `Bytes.concat` and `Bytes.split` are added to be able to
|
- `Bytes.concat` and `Bytes.split` are added to be able to
|
||||||
(de-)construct byte arrays.
|
(de-)construct byte arrays.
|
||||||
- `[a..b]` language construct, returning the list of numbers between
|
- `[a..b]` language construct, returning the list of numbers between
|
||||||
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
|
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
|
||||||
- [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md)
|
- [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md)
|
||||||
- Checks that `init` is not called from other functions.
|
- Checks that `init` is not called from other functions.
|
||||||
### Changed
|
|
||||||
- Error messages are changed into a uniform format, and more helpful
|
|
||||||
messages have been added.
|
|
||||||
- `Crypto.<hash_fun>` and `String.<hash_fun>` for byte arrays now only
|
|
||||||
hash the actual byte array - not the internal ABI format.
|
|
||||||
- More strict checks for polymorphic oracles and higher order oracles
|
|
||||||
and entrypoints.
|
|
||||||
- `AENS.claim` is updated with a `NameFee` field - to be able to do
|
|
||||||
name auctions within contracts.
|
|
||||||
- Fixed a bug in `Bytes.to_str` for AEVM.
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
## [4.0.0-rc1] - 2019-08-22
|
|
||||||
### Added
|
|
||||||
- FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many
|
- FATE backend - the compiler is able to produce VM code for both `AEVM` and `FATE`. Many
|
||||||
of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts
|
of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts
|
||||||
for.
|
for.
|
||||||
@ -70,6 +43,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
that shall be able to receive funds should be marked as payable. `Address.is_payable(a)`
|
that shall be able to receive funds should be marked as payable. `Address.is_payable(a)`
|
||||||
can be used to check if an (contract) address is payable or not.
|
can be used to check if an (contract) address is payable or not.
|
||||||
### Changed
|
### Changed
|
||||||
|
- Nice type error if contract function is called as from a namespace.
|
||||||
|
- Fail on function definitions in contracts other than the main contract.
|
||||||
|
- Bug fix in variable optimization - don't discard writes to the store/state.
|
||||||
|
- Bug fixes in error reporting.
|
||||||
|
- Bug fix in variable liveness analysis for FATE.
|
||||||
|
- Error messages are changed into a uniform format, and more helpful
|
||||||
|
messages have been added.
|
||||||
|
- `Crypto.<hash_fun>` and `String.<hash_fun>` for byte arrays now only
|
||||||
|
hash the actual byte array - not the internal ABI format.
|
||||||
|
- More strict checks for polymorphic oracles and higher order oracles
|
||||||
|
and entrypoints.
|
||||||
|
- `AENS.claim` is updated with a `NameFee` field - to be able to do
|
||||||
|
name auctions within contracts.
|
||||||
|
- Fixed a bug in `Bytes.to_str` for AEVM.
|
||||||
- New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and
|
- New syntax for tuple types. Now 0-tuple type is encoded as `unit` instead of `()` and
|
||||||
regular tuples are encoded by interspersing inner types with `*`, for instance `int * string`.
|
regular tuples are encoded by interspersing inner types with `*`, for instance `int * string`.
|
||||||
Parens are not necessary. Note it only affects the types, values remain as their were before,
|
Parens are not necessary. Note it only affects the types, values remain as their were before,
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||||
]}.
|
]}.
|
||||||
|
|
||||||
{relx, [{release, {aesophia, "4.0.0"},
|
{relx, [{release, {aesophia, "4.1.0-rc1"},
|
||||||
[aesophia, aebytecode, getopt]},
|
[aesophia, aebytecode, getopt]},
|
||||||
|
|
||||||
{dev_mode, true},
|
{dev_mode, true},
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
, decode_calldata/4
|
, decode_calldata/4
|
||||||
, parse/2
|
, parse/2
|
||||||
, add_include_path/2
|
, add_include_path/2
|
||||||
|
, validate_byte_code/3
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||||
@ -566,6 +567,87 @@ pp(Code, Options, Option, PPFun) ->
|
|||||||
ok
|
ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% -- Byte code validation ---------------------------------------------------
|
||||||
|
|
||||||
|
-define(protect(Tag, Code), fun() -> try Code catch _:Err1 -> throw({Tag, Err1}) end end()).
|
||||||
|
|
||||||
|
-spec validate_byte_code(map(), string(), options()) -> ok | {error, [aeso_errors:error()]}.
|
||||||
|
validate_byte_code(#{ byte_code := ByteCode, payable := Payable }, Source, Options) ->
|
||||||
|
Fail = fun(Err) -> {error, [aeso_errors:new(data_error, Err)]} end,
|
||||||
|
case proplists:get_value(backend, Options, aevm) of
|
||||||
|
B when B /= fate -> Fail(io_lib:format("Unsupported backend: ~s\n", [B]));
|
||||||
|
fate ->
|
||||||
|
try
|
||||||
|
FCode1 = ?protect(deserialize, aeb_fate_code:strip_init_function(aeb_fate_code:deserialize(ByteCode))),
|
||||||
|
{FCode2, SrcPayable} =
|
||||||
|
?protect(compile,
|
||||||
|
begin
|
||||||
|
{ok, #{ byte_code := SrcByteCode, payable := SrcPayable }} =
|
||||||
|
from_string1(fate, Source, Options),
|
||||||
|
FCode = aeb_fate_code:deserialize(SrcByteCode),
|
||||||
|
{aeb_fate_code:strip_init_function(FCode), SrcPayable}
|
||||||
|
end),
|
||||||
|
case compare_fate_code(FCode1, FCode2) of
|
||||||
|
ok when SrcPayable /= Payable ->
|
||||||
|
Not = fun(true) -> ""; (false) -> " not" end,
|
||||||
|
Fail(io_lib:format("Byte code contract is~s payable, but source code contract is~s.\n",
|
||||||
|
[Not(Payable), Not(SrcPayable)]));
|
||||||
|
ok -> ok;
|
||||||
|
{error, Why} -> Fail(io_lib:format("Byte code does not match source code.\n~s", [Why]))
|
||||||
|
end
|
||||||
|
catch
|
||||||
|
throw:{deserialize, _} -> Fail("Invalid byte code");
|
||||||
|
throw:{compile, {error, Errs}} -> {error, Errs}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
compare_fate_code(FCode1, FCode2) ->
|
||||||
|
Funs1 = aeb_fate_code:functions(FCode1),
|
||||||
|
Funs2 = aeb_fate_code:functions(FCode2),
|
||||||
|
Syms1 = aeb_fate_code:symbols(FCode1),
|
||||||
|
Syms2 = aeb_fate_code:symbols(FCode2),
|
||||||
|
FunHashes1 = maps:keys(Funs1),
|
||||||
|
FunHashes2 = maps:keys(Funs2),
|
||||||
|
case FunHashes1 == FunHashes2 of
|
||||||
|
false ->
|
||||||
|
InByteCode = [ binary_to_list(maps:get(H, Syms1)) || H <- FunHashes1 -- FunHashes2 ],
|
||||||
|
InSourceCode = [ binary_to_list(maps:get(H, Syms2)) || H <- FunHashes2 -- FunHashes1 ],
|
||||||
|
Msg = [ io_lib:format("- Functions in the byte code but not in the source code:\n"
|
||||||
|
" ~s\n", [string:join(InByteCode, ", ")]) || InByteCode /= [] ] ++
|
||||||
|
[ io_lib:format("- Functions in the source code but not in the byte code:\n"
|
||||||
|
" ~s\n", [string:join(InSourceCode, ", ")]) || InSourceCode /= [] ],
|
||||||
|
{error, Msg};
|
||||||
|
true ->
|
||||||
|
case lists:append([ compare_fate_fun(maps:get(H, Syms1), Fun1, Fun2)
|
||||||
|
|| {{H, Fun1}, {_, Fun2}} <- lists:zip(maps:to_list(Funs1),
|
||||||
|
maps:to_list(Funs2)) ]) of
|
||||||
|
[] -> ok;
|
||||||
|
Errs -> {error, Errs}
|
||||||
|
end
|
||||||
|
end.
|
||||||
|
|
||||||
|
compare_fate_fun(_Name, Fun, Fun) -> [];
|
||||||
|
compare_fate_fun(Name, {Attr, Type, _}, {Attr, Type, _}) ->
|
||||||
|
[io_lib:format("- The implementation of the function ~s is different.\n", [Name])];
|
||||||
|
compare_fate_fun(Name, {Attr1, Type, _}, {Attr2, Type, _}) ->
|
||||||
|
[io_lib:format("- The attributes of the function ~s differ:\n"
|
||||||
|
" Byte code: ~s\n"
|
||||||
|
" Source code: ~s\n",
|
||||||
|
[Name, string:join([ atom_to_list(A) || A <- Attr1 ], ", "),
|
||||||
|
string:join([ atom_to_list(A) || A <- Attr2 ], ", ")])];
|
||||||
|
compare_fate_fun(Name, {_, Type1, _}, {_, Type2, _}) ->
|
||||||
|
[io_lib:format("- The type of the function ~s differs:\n"
|
||||||
|
" Byte code: ~s\n"
|
||||||
|
" Source code: ~s\n",
|
||||||
|
[Name, pp_fate_sig(Type1), pp_fate_sig(Type2)])].
|
||||||
|
|
||||||
|
pp_fate_sig({[Arg], Res}) ->
|
||||||
|
io_lib:format("~s => ~s", [pp_fate_type(Arg), pp_fate_type(Res)]);
|
||||||
|
pp_fate_sig({Args, Res}) ->
|
||||||
|
io_lib:format("(~s) => ~s", [string:join([pp_fate_type(Arg) || Arg <- Args], ", "), pp_fate_type(Res)]).
|
||||||
|
|
||||||
|
pp_fate_type(T) -> io_lib:format("~w", [T]).
|
||||||
|
|
||||||
%% -------------------------------------------------------------------
|
%% -------------------------------------------------------------------
|
||||||
|
|
||||||
sophia_type_to_typerep(String) ->
|
sophia_type_to_typerep(String) ->
|
||||||
|
@ -11,19 +11,23 @@
|
|||||||
|
|
||||||
-export([compile/2, term_to_fate/1]).
|
-export([compile/2, term_to_fate/1]).
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-export([optimize_fun/4, to_basic_blocks/1]).
|
||||||
|
-endif.
|
||||||
|
|
||||||
%% -- Preamble ---------------------------------------------------------------
|
%% -- Preamble ---------------------------------------------------------------
|
||||||
|
|
||||||
-type scode() :: [sinstr()].
|
-type scode() :: [sinstr()].
|
||||||
-type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all
|
-type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all
|
||||||
| switch_body
|
| switch_body
|
||||||
| tuple(). %% FATE instruction
|
| loop
|
||||||
|
| tuple() | atom(). %% FATE instruction
|
||||||
|
|
||||||
-type arg() :: tuple(). %% Not exported: aeb_fate_ops:fate_arg().
|
-type arg() :: tuple(). %% Not exported: aeb_fate_ops:fate_arg().
|
||||||
|
|
||||||
%% Annotated scode
|
%% Annotated scode
|
||||||
-type scode_a() :: [sinstr_a()].
|
-type scode_a() :: [sinstr_a()].
|
||||||
-type sinstr_a() :: {switch, arg(), stype(), [maybe_scode_a()], maybe_scode_a()} %% last arg is catch-all
|
-type sinstr_a() :: {switch, arg(), stype(), [maybe_scode_a()], maybe_scode_a()} %% last arg is catch-all
|
||||||
| switch_body
|
|
||||||
| {i, ann(), tuple()}. %% FATE instruction
|
| {i, ann(), tuple()}. %% FATE instruction
|
||||||
|
|
||||||
-type ann() :: #{ live_in := vars(), live_out := vars() }.
|
-type ann() :: #{ live_in := vars(), live_out := vars() }.
|
||||||
@ -41,82 +45,6 @@
|
|||||||
-define(s, {store, 1}).
|
-define(s, {store, 1}).
|
||||||
-define(void, {var, 9999}).
|
-define(void, {var, 9999}).
|
||||||
|
|
||||||
-define(IsState(X), (is_tuple(X) andalso tuple_size(X) =:= 2 andalso element(1, X) =:= var andalso element(2, X) < 0)).
|
|
||||||
|
|
||||||
-define(IsOp(Op), (
|
|
||||||
Op =:= 'STORE' orelse
|
|
||||||
Op =:= 'ADD' orelse
|
|
||||||
Op =:= 'SUB' orelse
|
|
||||||
Op =:= 'MUL' orelse
|
|
||||||
Op =:= 'DIV' orelse
|
|
||||||
Op =:= 'MOD' orelse
|
|
||||||
Op =:= 'POW' orelse
|
|
||||||
Op =:= 'LT' orelse
|
|
||||||
Op =:= 'GT' orelse
|
|
||||||
Op =:= 'EQ' orelse
|
|
||||||
Op =:= 'ELT' orelse
|
|
||||||
Op =:= 'EGT' orelse
|
|
||||||
Op =:= 'NEQ' orelse
|
|
||||||
Op =:= 'AND' orelse
|
|
||||||
Op =:= 'OR' orelse
|
|
||||||
Op =:= 'NOT' orelse
|
|
||||||
Op =:= 'ELEMENT' orelse
|
|
||||||
Op =:= 'MAP_EMPTY' orelse
|
|
||||||
Op =:= 'MAP_LOOKUP' orelse
|
|
||||||
Op =:= 'MAP_LOOKUPD' orelse
|
|
||||||
Op =:= 'MAP_UPDATE' orelse
|
|
||||||
Op =:= 'MAP_DELETE' orelse
|
|
||||||
Op =:= 'MAP_MEMBER' orelse
|
|
||||||
Op =:= 'MAP_FROM_LIST' orelse
|
|
||||||
Op =:= 'MAP_TO_LIST' orelse
|
|
||||||
Op =:= 'MAP_SIZE' orelse
|
|
||||||
Op =:= 'NIL' orelse
|
|
||||||
Op =:= 'IS_NIL' orelse
|
|
||||||
Op =:= 'CONS' orelse
|
|
||||||
Op =:= 'HD' orelse
|
|
||||||
Op =:= 'TL' orelse
|
|
||||||
Op =:= 'LENGTH' orelse
|
|
||||||
Op =:= 'APPEND' orelse
|
|
||||||
Op =:= 'STR_JOIN' orelse
|
|
||||||
Op =:= 'INT_TO_STR' orelse
|
|
||||||
Op =:= 'ADDR_TO_STR' orelse
|
|
||||||
Op =:= 'STR_REVERSE' orelse
|
|
||||||
Op =:= 'STR_LENGTH' orelse
|
|
||||||
Op =:= 'INT_TO_ADDR' orelse
|
|
||||||
Op =:= 'VARIANT_TEST' orelse
|
|
||||||
Op =:= 'VARIANT_ELEMENT' orelse
|
|
||||||
Op =:= 'BITS_NONE' orelse
|
|
||||||
Op =:= 'BITS_ALL' orelse
|
|
||||||
Op =:= 'BITS_ALL_N' orelse
|
|
||||||
Op =:= 'BITS_SET' orelse
|
|
||||||
Op =:= 'BITS_CLEAR' orelse
|
|
||||||
Op =:= 'BITS_TEST' orelse
|
|
||||||
Op =:= 'BITS_SUM' orelse
|
|
||||||
Op =:= 'BITS_OR' orelse
|
|
||||||
Op =:= 'BITS_AND' orelse
|
|
||||||
Op =:= 'BITS_DIFF' orelse
|
|
||||||
Op =:= 'SHA3' orelse
|
|
||||||
Op =:= 'SHA256' orelse
|
|
||||||
Op =:= 'BLAKE2B' orelse
|
|
||||||
Op =:= 'VERIFY_SIG' orelse
|
|
||||||
Op =:= 'VERIFY_SIG_SECP256K1' orelse
|
|
||||||
Op =:= 'ECVERIFY_SECP256K1' orelse
|
|
||||||
Op =:= 'ECRECOVER_SECP256K1' orelse
|
|
||||||
Op =:= 'CONTRACT_TO_ADDRESS' orelse
|
|
||||||
Op =:= 'ADDRESS_TO_CONTRACT' orelse
|
|
||||||
Op =:= 'AUTH_TX_HASH' orelse
|
|
||||||
Op =:= 'BYTES_TO_INT' orelse
|
|
||||||
Op =:= 'BYTES_TO_STR' orelse
|
|
||||||
Op =:= 'BYTES_CONCAT' orelse
|
|
||||||
Op =:= 'BYTES_SPLIT' orelse
|
|
||||||
Op =:= 'ORACLE_CHECK' orelse
|
|
||||||
Op =:= 'ORACLE_CHECK_QUERY' orelse
|
|
||||||
Op =:= 'IS_ORACLE' orelse
|
|
||||||
Op =:= 'IS_CONTRACT' orelse
|
|
||||||
Op =:= 'IS_PAYABLE' orelse
|
|
||||||
Op =:= 'CREATOR' orelse
|
|
||||||
false)).
|
|
||||||
|
|
||||||
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true }).
|
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true }).
|
||||||
|
|
||||||
%% -- Debugging --------------------------------------------------------------
|
%% -- Debugging --------------------------------------------------------------
|
||||||
@ -125,12 +53,19 @@ is_debug(Tag, Options) ->
|
|||||||
Tags = proplists:get_value(debug, Options, []),
|
Tags = proplists:get_value(debug, Options, []),
|
||||||
Tags == all orelse lists:member(Tag, Tags).
|
Tags == all orelse lists:member(Tag, Tags).
|
||||||
|
|
||||||
debug(Tag, Options, Fmt, Args) ->
|
-define(debug(Tag, Options, Fmt, Args),
|
||||||
|
debug(Tag, Options, fun() -> io:format(Fmt, Args) end)).
|
||||||
|
|
||||||
|
debug(Tag, Options, Fun) ->
|
||||||
case is_debug(Tag, Options) of
|
case is_debug(Tag, Options) of
|
||||||
true -> io:format(Fmt, Args);
|
true -> Fun();
|
||||||
false -> ok
|
false -> ok
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-dialyzer({nowarn_function, [code_error/1]}).
|
||||||
|
code_error(Err) ->
|
||||||
|
aeso_errors:throw(aeso_code_errors:format(Err)).
|
||||||
|
|
||||||
%% -- Main -------------------------------------------------------------------
|
%% -- Main -------------------------------------------------------------------
|
||||||
|
|
||||||
%% @doc Main entry point.
|
%% @doc Main entry point.
|
||||||
@ -140,7 +75,7 @@ compile(FCode, Options) ->
|
|||||||
SFuns = functions_to_scode(ContractName, Functions, Options),
|
SFuns = functions_to_scode(ContractName, Functions, Options),
|
||||||
SFuns1 = optimize_scode(SFuns, Options),
|
SFuns1 = optimize_scode(SFuns, Options),
|
||||||
FateCode = to_basic_blocks(SFuns1),
|
FateCode = to_basic_blocks(SFuns1),
|
||||||
debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
|
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
|
||||||
FateCode.
|
FateCode.
|
||||||
|
|
||||||
make_function_id(X) ->
|
make_function_id(X) ->
|
||||||
@ -223,8 +158,6 @@ bind_local(Name, Env) ->
|
|||||||
|
|
||||||
notail(Env) -> Env#env{ tailpos = false }.
|
notail(Env) -> Env#env{ tailpos = false }.
|
||||||
|
|
||||||
code_error(Err) -> error(Err).
|
|
||||||
|
|
||||||
lookup_var(#env{vars = Vars}, X) ->
|
lookup_var(#env{vars = Vars}, X) ->
|
||||||
case lists:keyfind(X, 1, Vars) of
|
case lists:keyfind(X, 1, Vars) of
|
||||||
{_, Var} -> Var;
|
{_, Var} -> Var;
|
||||||
@ -664,23 +597,23 @@ flatten_s(I) -> I.
|
|||||||
|
|
||||||
optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) ->
|
optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) ->
|
||||||
Code0 = flatten(Code),
|
Code0 = flatten(Code),
|
||||||
debug(opt, Options, "Optimizing ~s\n", [Name]),
|
?debug(opt, Options, "Optimizing ~s\n", [Name]),
|
||||||
Code1 = simpl_loop(0, Code0, Options),
|
Code1 = simpl_loop(0, Code0, Options),
|
||||||
Code2 = desugar(Code1),
|
Code2 = desugar(Code1),
|
||||||
{Attrs, Sig, Code2}.
|
{Attrs, Sig, Code2}.
|
||||||
|
|
||||||
simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS ->
|
simpl_loop(N, Code, Options) when N >= ?MAX_SIMPL_ITERATIONS ->
|
||||||
debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]),
|
?debug(opt, Options, " No simpl_loop fixed_point after ~p iterations.\n\n", [N]),
|
||||||
Code;
|
Code;
|
||||||
simpl_loop(N, Code, Options) ->
|
simpl_loop(N, Code, Options) ->
|
||||||
ACode = annotate_code(Code),
|
ACode = annotate_code(Code),
|
||||||
[ debug(opt, Options, " annotated:\n~s\n", [pp_ann(" ", ACode)]) || N == 0 ],
|
[ ?debug(opt, Options, " annotated:\n~s\n", [pp_ann(" ", ACode)]) || N == 0 ],
|
||||||
Code1 = simplify(ACode, Options),
|
Code1 = simplify(ACode, Options),
|
||||||
[ debug(opt, Options, " optimized:\n~s\n", [pp_ann(" ", Code1)]) || Code1 /= ACode ],
|
[ ?debug(opt, Options, " optimized:\n~s\n", [pp_ann(" ", Code1)]) || Code1 /= ACode ],
|
||||||
Code2 = unannotate(Code1),
|
Code2 = unannotate(Code1),
|
||||||
case Code == Code2 of
|
case Code == Code2 of
|
||||||
true ->
|
true ->
|
||||||
debug(opt, Options, " Reached simpl_loop fixed point after ~p iteration~s.\n\n",
|
?debug(opt, Options, " Reached simpl_loop fixed point after ~p iteration~s.\n\n",
|
||||||
[N, if N /= 1 -> "s"; true -> "" end]),
|
[N, if N /= 1 -> "s"; true -> "" end]),
|
||||||
Code2;
|
Code2;
|
||||||
false -> simpl_loop(N + 1, Code2, Options)
|
false -> simpl_loop(N + 1, Code2, Options)
|
||||||
@ -700,11 +633,9 @@ pp_ann(Ind, [{switch, Arg, Type, Alts, Def} | Code]) ->
|
|||||||
|| {Tag, Alt} <- lists:zip(Tags, Alts), Alt /= missing],
|
|| {Tag, Alt} <- lists:zip(Tags, Alts), Alt /= missing],
|
||||||
[[Ind1, "_ =>\n", pp_ann(Ind2, Def)] || Def /= missing],
|
[[Ind1, "_ =>\n", pp_ann(Ind2, Def)] || Def /= missing],
|
||||||
pp_ann(Ind, Code)];
|
pp_ann(Ind, Code)];
|
||||||
pp_ann(Ind, [switch_body | Code]) ->
|
|
||||||
[Ind, "SWITCH-BODY\n", pp_ann(Ind, Code)];
|
|
||||||
pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) ->
|
pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) ->
|
||||||
Fmt = fun([]) -> "()";
|
Fmt = fun([]) -> "()";
|
||||||
(Xs) -> string:join([lists:concat(["var", N]) || {var, N} <- Xs], " ")
|
(Xs) -> string:join([lists:flatten(pp_arg(X)) || X <- Xs], " ")
|
||||||
end,
|
end,
|
||||||
Op = [Ind, pp_op(desugar_args(I))],
|
Op = [Ind, pp_op(desugar_args(I))],
|
||||||
Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []],
|
Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []],
|
||||||
@ -712,70 +643,67 @@ pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) ->
|
|||||||
pp_ann(Ind, Code)];
|
pp_ann(Ind, Code)];
|
||||||
pp_ann(_, []) -> [].
|
pp_ann(_, []) -> [].
|
||||||
|
|
||||||
|
pp_op(switch_body) -> "SWITCH-BODY";
|
||||||
pp_op(loop) -> "LOOP";
|
pp_op(loop) -> "LOOP";
|
||||||
pp_op(I) ->
|
pp_op(I) ->
|
||||||
aeb_fate_pp:format_op(I, #{}).
|
aeb_fate_pp:format_op(I, #{}).
|
||||||
|
|
||||||
pp_arg(?i(I)) -> io_lib:format("~w", [I]);
|
pp_arg(?i(I)) -> io_lib:format("~w", [I]);
|
||||||
pp_arg({arg, N}) -> io_lib:format("arg~p", [N]);
|
pp_arg({arg, N}) -> io_lib:format("arg~p", [N]);
|
||||||
pp_arg(?s) -> "store1";
|
pp_arg({store, N}) -> io_lib:format("store~p", [N]);
|
||||||
pp_arg({var, N}) -> io_lib:format("var~p", [N]);
|
pp_arg({var, N}) -> io_lib:format("var~p", [N]);
|
||||||
pp_arg(?a) -> "a".
|
pp_arg(?a) -> "a".
|
||||||
|
|
||||||
%% -- Analysis --
|
%% -- Analysis --
|
||||||
|
|
||||||
annotate_code(Code) ->
|
annotate_code(Code) ->
|
||||||
{WCode, _} = ann_writes(Code, ordsets:new(), []),
|
annotate_code(5, [], Code).
|
||||||
{RCode, _} = ann_reads(WCode, ordsets:new(), []),
|
|
||||||
RCode.
|
|
||||||
|
|
||||||
%% Reverses the code
|
annotate_code(Fuel, LiveTop, Code) ->
|
||||||
ann_writes(missing, Writes, []) -> {missing, Writes};
|
{Code1, LiveIn} = ann_live(LiveTop, Code, []),
|
||||||
ann_writes([switch_body | Code], Writes, Acc) ->
|
case LiveIn == LiveTop of
|
||||||
ann_writes(Code, Writes, [switch_body | Acc]);
|
true -> Code1;
|
||||||
ann_writes([{switch, Arg, Type, Alts, Def} | Code], Writes, Acc) ->
|
false when Fuel =< 0 ->
|
||||||
{Alts1, WritesAlts} = lists:unzip([ ann_writes(Alt, Writes, []) || Alt <- Alts ]),
|
code_error(liveness_analysis_out_of_fuel);
|
||||||
{Def1, WritesDef} = ann_writes(Def, Writes, []),
|
false -> annotate_code(Fuel - 1, LiveIn, Code)
|
||||||
Writes1 = ordsets:union(Writes, ordsets:intersection([WritesDef | WritesAlts])),
|
end.
|
||||||
ann_writes(Code, Writes1, [{switch, Arg, Type, Alts1, Def1} | Acc]);
|
|
||||||
ann_writes([I | Code], Writes, Acc) ->
|
|
||||||
Ws = [ W || W <- var_writes(I), not ?IsState(W) ],
|
|
||||||
Writes1 = ordsets:union(Writes, Ws),
|
|
||||||
Ann = #{ writes_in => Writes, writes_out => Writes1 },
|
|
||||||
ann_writes(Code, Writes1, [{i, Ann, I} | Acc]);
|
|
||||||
ann_writes([], Writes, Acc) ->
|
|
||||||
{Acc, Writes}.
|
|
||||||
|
|
||||||
%% Takes reversed code and unreverses it.
|
ann_live(_LiveTop, missing, _LiveOut) -> {missing, []};
|
||||||
ann_reads(missing, Reads, []) -> {missing, Reads};
|
ann_live(_LiveTop, [], LiveOut) -> {[], LiveOut};
|
||||||
ann_reads([switch_body | Code], Reads, Acc) ->
|
ann_live(LiveTop, [I | Is], LiveOut) ->
|
||||||
ann_reads(Code, Reads, [switch_body | Acc]);
|
{Is1, LiveMid} = ann_live(LiveTop, Is, LiveOut),
|
||||||
ann_reads([{switch, Arg, Type, Alts, Def} | Code], Reads, Acc) ->
|
{I1, LiveIn} = ann_live1(LiveTop, I, LiveMid),
|
||||||
{Alts1, ReadsAlts} = lists:unzip([ ann_reads(Alt, Reads, []) || Alt <- Alts ]),
|
{[I1 | Is1], LiveIn}.
|
||||||
{Def1, ReadsDef} = ann_reads(Def, Reads, []),
|
|
||||||
Reads1 = ordsets:union([[Arg], Reads, ReadsDef | ReadsAlts]),
|
ann_live1(_LiveTop, switch_body, LiveOut) ->
|
||||||
ann_reads(Code, Reads1, [{switch, Arg, Type, Alts1, Def1} | Acc]);
|
Ann = #{ live_in => LiveOut, live_out => LiveOut },
|
||||||
ann_reads([{i, Ann, I} | Code], Reads, Acc) ->
|
{{i, Ann, switch_body}, LiveOut};
|
||||||
#{ writes_in := WritesIn, writes_out := WritesOut } = Ann,
|
ann_live1(LiveTop, loop, _LiveOut) ->
|
||||||
#{ read := Rs, write := W, pure := Pure } = attributes(I),
|
Ann = #{ live_in => LiveTop, live_out => [] },
|
||||||
|
{{i, Ann, loop}, LiveTop};
|
||||||
|
ann_live1(LiveTop, {switch, Arg, Type, Alts, Def}, LiveOut) ->
|
||||||
|
Read = [Arg || is_reg(Arg)],
|
||||||
|
{Alts1, LiveAlts} = lists:unzip([ ann_live(LiveTop, Alt, LiveOut) || Alt <- Alts ]),
|
||||||
|
{Def1, LiveDef} = ann_live(LiveTop, Def, LiveOut),
|
||||||
|
LiveIn = ordsets:union([Read, LiveDef | LiveAlts]),
|
||||||
|
{{switch, Arg, Type, Alts1, Def1}, LiveIn};
|
||||||
|
ann_live1(_LiveTop, I, LiveOut) ->
|
||||||
|
#{ read := Reads0, write := W } = attributes(I),
|
||||||
|
Reads = lists:filter(fun is_reg/1, Reads0),
|
||||||
%% If we write it here it's not live in (unless we also read it)
|
%% If we write it here it's not live in (unless we also read it)
|
||||||
Reads1 = Reads -- [W],
|
LiveIn = ordsets:union(LiveOut -- [W], Reads),
|
||||||
Reads2 =
|
Ann = #{ live_in => LiveIn, live_out => LiveOut },
|
||||||
case {W, Pure andalso not ordsets:is_element(W, Reads)} of
|
{{i, Ann, I}, LiveIn}.
|
||||||
%% This is a little bit dangerous: if writing to a dead variable, we ignore
|
|
||||||
%% the reads. Relies on dead writes to be removed by the
|
|
||||||
%% optimisations below (r_write_to_dead_var).
|
|
||||||
{{var, _}, true} -> Reads1;
|
|
||||||
_ -> ordsets:union(Reads1, Rs)
|
|
||||||
end,
|
|
||||||
LiveIn = ordsets:intersection(Reads2, WritesIn),
|
|
||||||
LiveOut = ordsets:intersection(Reads, WritesOut),
|
|
||||||
Ann1 = #{ live_in => LiveIn, live_out => LiveOut },
|
|
||||||
ann_reads(Code, Reads2, [{i, Ann1, I} | Acc]);
|
|
||||||
ann_reads([], Reads, Acc) -> {Acc, Reads}.
|
|
||||||
|
|
||||||
%% Instruction attributes: reads, writes and purity (pure means no side-effects
|
is_reg(?a) -> false;
|
||||||
%% aside from the reads and writes).
|
is_reg(none) -> false;
|
||||||
|
is_reg(pc) -> false;
|
||||||
|
is_reg({immediate, _}) -> false;
|
||||||
|
is_reg({arg, _}) -> true;
|
||||||
|
is_reg({store, _}) -> true;
|
||||||
|
is_reg({var, _}) -> true.
|
||||||
|
|
||||||
|
%% Instruction attributes: reads, writes and purity (pure means no writing to the chain).
|
||||||
attributes(I) ->
|
attributes(I) ->
|
||||||
Set = fun(L) when is_list(L) -> ordsets:from_list(L);
|
Set = fun(L) when is_list(L) -> ordsets:from_list(L);
|
||||||
(X) -> ordsets:from_list([X]) end,
|
(X) -> ordsets:from_list([X]) end,
|
||||||
@ -784,12 +712,13 @@ attributes(I) ->
|
|||||||
Impure = fun(W, R) -> Attr(W, R, false) end,
|
Impure = fun(W, R) -> Attr(W, R, false) end,
|
||||||
case I of
|
case I of
|
||||||
loop -> Impure(pc, []);
|
loop -> Impure(pc, []);
|
||||||
|
switch_body -> Pure(none, []);
|
||||||
'RETURN' -> Impure(pc, []);
|
'RETURN' -> Impure(pc, []);
|
||||||
{'RETURNR', A} -> Impure(pc, A);
|
{'RETURNR', A} -> Impure(pc, A);
|
||||||
{'CALL', _} -> Impure(?a, []);
|
{'CALL', A} -> Impure(?a, [A]);
|
||||||
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
|
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
|
||||||
{'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
|
{'CALL_GR', A, _, B, C, D, E} -> Impure(?a, [A, B, C, D, E]);
|
||||||
{'CALL_T', _} -> Impure(pc, []);
|
{'CALL_T', A} -> Impure(pc, [A]);
|
||||||
{'CALL_VALUE', A} -> Pure(A, []);
|
{'CALL_VALUE', A} -> Pure(A, []);
|
||||||
{'JUMP', _} -> Impure(pc, []);
|
{'JUMP', _} -> Impure(pc, []);
|
||||||
{'JUMPIF', A, _} -> Impure(pc, A);
|
{'JUMPIF', A, _} -> Impure(pc, A);
|
||||||
@ -874,26 +803,26 @@ attributes(I) ->
|
|||||||
{'BYTES_TO_STR', A, B} -> Pure(A, [B]);
|
{'BYTES_TO_STR', A, B} -> Pure(A, [B]);
|
||||||
{'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]);
|
{'BYTES_CONCAT', A, B, C} -> Pure(A, [B, C]);
|
||||||
{'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]);
|
{'BYTES_SPLIT', A, B, C} -> Pure(A, [B, C]);
|
||||||
{'ORACLE_CHECK', A, B, C, D} -> Impure(A, [B, C, D]);
|
{'ORACLE_CHECK', A, B, C, D} -> Pure(A, [B, C, D]);
|
||||||
{'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]);
|
{'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Pure(A, [B, C, D, E]);
|
||||||
{'IS_ORACLE', A, B} -> Impure(A, [B]);
|
{'IS_ORACLE', A, B} -> Pure(A, [B]);
|
||||||
{'IS_CONTRACT', A, B} -> Impure(A, [B]);
|
{'IS_CONTRACT', A, B} -> Pure(A, [B]);
|
||||||
{'IS_PAYABLE', A, B} -> Impure(A, [B]);
|
{'IS_PAYABLE', A, B} -> Pure(A, [B]);
|
||||||
{'CREATOR', A} -> Pure(A, []);
|
{'CREATOR', A} -> Pure(A, []);
|
||||||
{'ADDRESS', A} -> Pure(A, []);
|
{'ADDRESS', A} -> Pure(A, []);
|
||||||
{'BALANCE', A} -> Impure(A, []);
|
{'BALANCE', A} -> Pure(A, []);
|
||||||
{'BALANCE_OTHER', A, B} -> Impure(A, [B]);
|
{'BALANCE_OTHER', A, B} -> Pure(A, [B]);
|
||||||
{'ORIGIN', A} -> Pure(A, []);
|
{'ORIGIN', A} -> Pure(A, []);
|
||||||
{'CALLER', A} -> Pure(A, []);
|
{'CALLER', A} -> Pure(A, []);
|
||||||
{'GASPRICE', A} -> Pure(A, []);
|
{'GASPRICE', A} -> Pure(A, []);
|
||||||
{'BLOCKHASH', A, B} -> Impure(A, [B]);
|
{'BLOCKHASH', A, B} -> Pure(A, [B]);
|
||||||
{'BENEFICIARY', A} -> Pure(A, []);
|
{'BENEFICIARY', A} -> Pure(A, []);
|
||||||
{'TIMESTAMP', A} -> Pure(A, []);
|
{'TIMESTAMP', A} -> Pure(A, []);
|
||||||
{'GENERATION', A} -> Pure(A, []);
|
{'GENERATION', A} -> Pure(A, []);
|
||||||
{'MICROBLOCK', A} -> Pure(A, []);
|
{'MICROBLOCK', A} -> Pure(A, []);
|
||||||
{'DIFFICULTY', A} -> Pure(A, []);
|
{'DIFFICULTY', A} -> Pure(A, []);
|
||||||
{'GASLIMIT', A} -> Pure(A, []);
|
{'GASLIMIT', A} -> Pure(A, []);
|
||||||
{'GAS', A} -> Impure(?a, A);
|
{'GAS', A} -> Pure(A, []);
|
||||||
{'LOG0', A} -> Impure(none, [A]);
|
{'LOG0', A} -> Impure(none, [A]);
|
||||||
{'LOG1', A, B} -> Impure(none, [A, B]);
|
{'LOG1', A, B} -> Impure(none, [A, B]);
|
||||||
{'LOG2', A, B, C} -> Impure(none, [A, B, C]);
|
{'LOG2', A, B, C} -> Impure(none, [A, B, C]);
|
||||||
@ -905,10 +834,10 @@ attributes(I) ->
|
|||||||
{'ORACLE_QUERY', A, B, C, D, E, F, G, H} -> Impure(A, [B, C, D, E, F, G, H]);
|
{'ORACLE_QUERY', A, B, C, D, E, F, G, H} -> Impure(A, [B, C, D, E, F, G, H]);
|
||||||
{'ORACLE_RESPOND', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]);
|
{'ORACLE_RESPOND', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]);
|
||||||
{'ORACLE_EXTEND', A, B, C} -> Impure(none, [A, B, C]);
|
{'ORACLE_EXTEND', A, B, C} -> Impure(none, [A, B, C]);
|
||||||
{'ORACLE_GET_ANSWER', A, B, C, D, E} -> Impure(A, [B, C, D, E]);
|
{'ORACLE_GET_ANSWER', A, B, C, D, E} -> Pure(A, [B, C, D, E]);
|
||||||
{'ORACLE_GET_QUESTION', A, B, C, D, E}-> Impure(A, [B, C, D, E]);
|
{'ORACLE_GET_QUESTION', A, B, C, D, E}-> Pure(A, [B, C, D, E]);
|
||||||
{'ORACLE_QUERY_FEE', A, B} -> Impure(A, [B]);
|
{'ORACLE_QUERY_FEE', A, B} -> Pure(A, [B]);
|
||||||
{'AENS_RESOLVE', A, B, C, D} -> Impure(A, [B, C, D]);
|
{'AENS_RESOLVE', A, B, C, D} -> Pure(A, [B, C, D]);
|
||||||
{'AENS_PRECLAIM', A, B, C} -> Impure(none, [A, B, C]);
|
{'AENS_PRECLAIM', A, B, C} -> Impure(none, [A, B, C]);
|
||||||
{'AENS_CLAIM', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]);
|
{'AENS_CLAIM', A, B, C, D, E} -> Impure(none, [A, B, C, D, E]);
|
||||||
{'AENS_UPDATE', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]);
|
{'AENS_UPDATE', A, B, C, D, E, F} -> Impure(none, [A, B, C, D, E, F]);
|
||||||
@ -923,15 +852,17 @@ var_writes({i, _, I}) -> var_writes(I);
|
|||||||
var_writes(I) ->
|
var_writes(I) ->
|
||||||
#{ write := W } = attributes(I),
|
#{ write := W } = attributes(I),
|
||||||
case W of
|
case W of
|
||||||
{var, _} -> [W];
|
{var, _} -> [W];
|
||||||
_ -> []
|
{arg, _} -> [W];
|
||||||
|
{store, _} -> [W];
|
||||||
|
{stack, _} -> [];
|
||||||
|
none -> [];
|
||||||
|
pc -> []
|
||||||
end.
|
end.
|
||||||
|
|
||||||
-spec independent(sinstr_a(), sinstr_a()) -> boolean().
|
-spec independent(sinstr_a(), sinstr_a()) -> boolean().
|
||||||
%% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging
|
%% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging
|
||||||
independent(_, {switch, _, _, _, _}) -> false;
|
independent(_, {switch, _, _, _, _}) -> false;
|
||||||
%% independent(switch_body, _) -> true;
|
|
||||||
independent(_, switch_body) -> true;
|
|
||||||
independent({i, _, I}, {i, _, J}) ->
|
independent({i, _, I}, {i, _, J}) ->
|
||||||
#{ write := WI, read := RI, pure := PureI } = attributes(I),
|
#{ write := WI, read := RI, pure := PureI } = attributes(I),
|
||||||
#{ write := WJ, read := RJ, pure := PureJ } = attributes(J),
|
#{ write := WJ, read := RJ, pure := PureJ } = attributes(J),
|
||||||
@ -953,8 +884,6 @@ merge_ann(#{ live_in := LiveIn }, #{ live_out := LiveOut }) ->
|
|||||||
#{ live_in => LiveIn, live_out => LiveOut }.
|
#{ live_in => LiveIn, live_out => LiveOut }.
|
||||||
|
|
||||||
%% Swap two instructions. Precondition: the instructions are independent/2.
|
%% Swap two instructions. Precondition: the instructions are independent/2.
|
||||||
swap_instrs(I, switch_body) -> {switch_body, I};
|
|
||||||
%% swap_instrs(switch_body, I) -> {I, switch_body}; %% Commented due to Dialyzer whinging
|
|
||||||
swap_instrs({i, #{ live_in := Live1 }, I}, {i, #{ live_in := Live2, live_out := Live3 }, J}) ->
|
swap_instrs({i, #{ live_in := Live1 }, I}, {i, #{ live_in := Live2, live_out := Live3 }, J}) ->
|
||||||
%% Since I and J are independent the J can't read or write anything in
|
%% Since I and J are independent the J can't read or write anything in
|
||||||
%% that I writes.
|
%% that I writes.
|
||||||
@ -966,17 +895,16 @@ swap_instrs({i, #{ live_in := Live1 }, I}, {i, #{ live_in := Live2, live_out :=
|
|||||||
{{i, #{ live_in => Live1, live_out => Live2_ }, J},
|
{{i, #{ live_in => Live1, live_out => Live2_ }, J},
|
||||||
{i, #{ live_in => Live2_, live_out => Live3 }, I}}.
|
{i, #{ live_in => Live2_, live_out => Live3 }, I}}.
|
||||||
|
|
||||||
live_in(R, _) when ?IsState(R) -> true;
|
live_in({store, _}, _) -> true;
|
||||||
live_in(R, #{ live_in := LiveIn }) -> ordsets:is_element(R, LiveIn);
|
live_in(R, #{ live_in := LiveIn }) -> ordsets:is_element(R, LiveIn);
|
||||||
live_in(R, {i, Ann, _}) -> live_in(R, Ann);
|
live_in(R, {i, Ann, _}) -> live_in(R, Ann);
|
||||||
live_in(R, [I = {i, _, _} | _]) -> live_in(R, I);
|
live_in(R, [I = {i, _, _} | _]) -> live_in(R, I);
|
||||||
live_in(R, [switch_body | Code]) -> live_in(R, Code);
|
|
||||||
live_in(R, [{switch, A, _, Alts, Def} | _]) ->
|
live_in(R, [{switch, A, _, Alts, Def} | _]) ->
|
||||||
R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]);
|
R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]);
|
||||||
live_in(_, missing) -> false;
|
live_in(_, missing) -> false;
|
||||||
live_in(_, []) -> false.
|
live_in(_, []) -> false.
|
||||||
|
|
||||||
live_out(R, _) when ?IsState(R) -> true;
|
live_out({store, _}, _) -> true;
|
||||||
live_out(R, #{ live_out := LiveOut }) -> ordsets:is_element(R, LiveOut).
|
live_out(R, #{ live_out := LiveOut }) -> ordsets:is_element(R, LiveOut).
|
||||||
|
|
||||||
%% -- Optimizations --
|
%% -- Optimizations --
|
||||||
@ -998,7 +926,7 @@ simpl_top(I, Code, Options) ->
|
|||||||
simpl_top(?SIMPL_FUEL, I, Code, Options).
|
simpl_top(?SIMPL_FUEL, I, Code, Options).
|
||||||
|
|
||||||
simpl_top(0, I, Code, _Options) ->
|
simpl_top(0, I, Code, _Options) ->
|
||||||
error({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).
|
apply_rules(Fuel, rules(), I, Code, Options).
|
||||||
|
|
||||||
@ -1010,7 +938,7 @@ apply_rules(Fuel, Rules, I, Code, Options) ->
|
|||||||
case is_debug(opt_rules, Options) of
|
case is_debug(opt_rules, Options) of
|
||||||
true ->
|
true ->
|
||||||
{OldCode, NewCode} = drop_common_suffix([I | Code], New ++ Rest),
|
{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)]);
|
?debug(opt_rules, Options, " Applied ~p:\n~s ==>\n~s\n", [RName, pp_ann(" ", OldCode), pp_ann(" ", NewCode)]);
|
||||||
false -> ok
|
false -> ok
|
||||||
end,
|
end,
|
||||||
lists:foldr(Cons, Rest, New)
|
lists:foldr(Cons, Rest, New)
|
||||||
@ -1040,6 +968,7 @@ rules() ->
|
|||||||
?RULE(r_swap_write),
|
?RULE(r_swap_write),
|
||||||
?RULE(r_constant_propagation),
|
?RULE(r_constant_propagation),
|
||||||
?RULE(r_prune_impossible_branches),
|
?RULE(r_prune_impossible_branches),
|
||||||
|
?RULE(r_single_successful_branch),
|
||||||
?RULE(r_inline_store),
|
?RULE(r_inline_store),
|
||||||
?RULE(r_float_switch_body)
|
?RULE(r_float_switch_body)
|
||||||
].
|
].
|
||||||
@ -1062,8 +991,9 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
|||||||
true -> false end;
|
true -> false end;
|
||||||
r_push_consume(_, _) -> false.
|
r_push_consume(_, _) -> false.
|
||||||
|
|
||||||
inline_push(Ann, Arg, Stack, [switch_body | Code], Acc) ->
|
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
|
||||||
inline_push(Ann, Arg, Stack, Code, [switch_body | Acc]);
|
{AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI),
|
||||||
|
inline_push(Ann1, Arg, Stack, Code, [AI1 | Acc]);
|
||||||
inline_push(Ann1, Arg, Stack, [{i, Ann2, I} = AI | Code], Acc) ->
|
inline_push(Ann1, Arg, Stack, [{i, Ann2, I} = AI | Code], Acc) ->
|
||||||
case op_view(I) of
|
case op_view(I) of
|
||||||
{Op, R, As} ->
|
{Op, R, As} ->
|
||||||
@ -1137,8 +1067,9 @@ r_swap_write(I = {i, _, _}, [J | Code]) ->
|
|||||||
end;
|
end;
|
||||||
r_swap_write(_, _) -> false.
|
r_swap_write(_, _) -> false.
|
||||||
|
|
||||||
r_swap_write(Pre, I, [switch_body | Code]) ->
|
r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
|
||||||
r_swap_write([switch_body | Pre], I, Code);
|
{J1, I1} = swap_instrs(I, J),
|
||||||
|
r_swap_write([J1 | Pre], I1, Code);
|
||||||
r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
r_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} ->
|
||||||
@ -1154,18 +1085,20 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
|||||||
r_swap_write(_, _, _) -> false.
|
r_swap_write(_, _, _) -> false.
|
||||||
|
|
||||||
%% Precompute instructions with known values
|
%% Precompute instructions with known values
|
||||||
r_constant_propagation(Cons = {i, _, {'CONS', R, _, _}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
r_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)}},
|
||||||
case R of
|
Cons1 = case R of
|
||||||
?a -> {[Store], Code};
|
?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
|
||||||
_ -> {[Cons, Store], Code}
|
_ -> Cons
|
||||||
end;
|
end,
|
||||||
r_constant_propagation(Cons = {i, _, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
{[Cons1, Store], Code};
|
||||||
|
r_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)}},
|
||||||
case R of
|
Nil1 = case R of
|
||||||
?a -> {[Store], Code};
|
?a -> {i, Ann1, {'NIL', ?void}};
|
||||||
_ -> {[Cons, Store], Code}
|
_ -> Nil
|
||||||
end;
|
end,
|
||||||
|
{[Nil1, Store], Code};
|
||||||
r_constant_propagation({i, Ann, I}, Code) ->
|
r_constant_propagation({i, Ann, I}, Code) ->
|
||||||
case op_view(I) of
|
case op_view(I) of
|
||||||
false -> false;
|
false -> false;
|
||||||
@ -1182,20 +1115,21 @@ r_constant_propagation({i, Ann, I}, Code) ->
|
|||||||
end;
|
end;
|
||||||
r_constant_propagation(_, _) -> false.
|
r_constant_propagation(_, _) -> false.
|
||||||
|
|
||||||
eval_op('ADD', [X, Y]) -> X + Y;
|
eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y;
|
||||||
eval_op('SUB', [X, Y]) -> X - Y;
|
eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
|
||||||
eval_op('MUL', [X, Y]) -> X * Y;
|
eval_op('MUL', [X, Y]) when is_integer(X), is_integer(Y) -> X * Y;
|
||||||
eval_op('DIV', [X, Y]) when Y /= 0 -> X div Y;
|
eval_op('DIV', [X, Y]) when is_integer(X), is_integer(Y), Y /= 0 -> X div Y;
|
||||||
eval_op('MOD', [X, Y]) when Y /= 0 -> X rem Y;
|
eval_op('MOD', [X, Y]) when is_integer(X), is_integer(Y), Y /= 0 -> X rem Y;
|
||||||
eval_op('POW', [_, _]) -> no_eval;
|
eval_op('POW', [_, _]) -> no_eval;
|
||||||
eval_op('LT', [X, Y]) -> X < Y;
|
eval_op('LT', [X, Y]) -> X < Y;
|
||||||
eval_op('GT', [X, Y]) -> X > Y;
|
eval_op('GT', [X, Y]) -> X > Y;
|
||||||
eval_op('EQ', [X, Y]) -> X =:= Y;
|
eval_op('EQ', [X, Y]) -> X =:= Y;
|
||||||
eval_op('ELT', [X, Y]) -> X =< Y;
|
eval_op('ELT', [X, Y]) -> X =< Y;
|
||||||
eval_op('EGT', [X, Y]) -> X >= Y;
|
eval_op('EGT', [X, Y]) -> X >= Y;
|
||||||
eval_op('NEQ', [X, Y]) -> X =/= Y;
|
eval_op('NEQ', [X, Y]) -> X =/= Y;
|
||||||
eval_op('NOT', [X]) -> not X;
|
eval_op('NOT', [true]) -> false;
|
||||||
eval_op(_, _) -> no_eval. %% TODO: bits?
|
eval_op('NOT', [false]) -> true;
|
||||||
|
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) ->
|
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||||
@ -1203,7 +1137,7 @@ r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
|||||||
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) ->
|
r_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
|
||||||
@ -1215,7 +1149,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
|
|||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
|
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
|
||||||
[{switch, R, Type, Alts, missing} | Code]) ->
|
[{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} ->
|
||||||
Alts1 = [missing || _ <- Alts],
|
Alts1 = [missing || _ <- Alts],
|
||||||
@ -1232,7 +1166,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
|
|||||||
end;
|
end;
|
||||||
r_prune_impossible_branches(_, _) -> false.
|
r_prune_impossible_branches(_, _) -> false.
|
||||||
|
|
||||||
pick_branch(boolean, V, [False, True]) ->
|
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,
|
||||||
case Alt of
|
case Alt of
|
||||||
missing -> false;
|
missing -> false;
|
||||||
@ -1241,10 +1175,62 @@ pick_branch(boolean, V, [False, True]) ->
|
|||||||
pick_branch(_Type, _V, _Alts) ->
|
pick_branch(_Type, _V, _Alts) ->
|
||||||
false.
|
false.
|
||||||
|
|
||||||
|
%% If there's a single branch that doesn't abort we can push the code for that
|
||||||
|
%% out of the switch.
|
||||||
|
r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||||
|
case push_code_out_of_switch([Def | Alts]) of
|
||||||
|
{_, none} -> false;
|
||||||
|
{_, many} -> false;
|
||||||
|
{_, [{i, _, switch_body}]} -> false;
|
||||||
|
{[Def1 | Alts1], PushedOut} ->
|
||||||
|
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
|
||||||
|
end;
|
||||||
|
r_single_successful_branch(_, _) -> false.
|
||||||
|
|
||||||
|
push_code_out_of_switch([]) -> {[], none};
|
||||||
|
push_code_out_of_switch([Alt | Alts]) ->
|
||||||
|
{Alt1, PushedAlt} = push_code_out_of_alt(Alt),
|
||||||
|
{Alts1, PushedAlts} = push_code_out_of_switch(Alts),
|
||||||
|
Pushed =
|
||||||
|
case {PushedAlt, PushedAlts} of
|
||||||
|
{none, _} -> PushedAlts;
|
||||||
|
{_, none} -> PushedAlt;
|
||||||
|
_ -> many
|
||||||
|
end,
|
||||||
|
{[Alt1 | Alts1], Pushed}.
|
||||||
|
|
||||||
|
push_code_out_of_alt(missing) -> {missing, none};
|
||||||
|
push_code_out_of_alt([Body = {i, _, switch_body} | Code]) ->
|
||||||
|
case does_abort(Code) of
|
||||||
|
true -> {[Body | Code], none};
|
||||||
|
false -> {[Body], [Body | Code]} %% Duplicate the switch_body, in case we apply this in the middle of a switch
|
||||||
|
end;
|
||||||
|
push_code_out_of_alt([{switch, R, Type, Alts, Def}]) ->
|
||||||
|
{[Def1 | Alts1], Pushed} = push_code_out_of_switch([Def | Alts]),
|
||||||
|
{[{switch, R, Type, Alts1, Def1}], Pushed};
|
||||||
|
push_code_out_of_alt(Code) ->
|
||||||
|
{Code, many}. %% Conservative
|
||||||
|
|
||||||
|
does_abort([I | Code]) ->
|
||||||
|
does_abort(I) orelse does_abort(Code);
|
||||||
|
does_abort({i, _, {'ABORT', _}}) -> true;
|
||||||
|
does_abort({i, _, {'EXIT', _}}) -> true;
|
||||||
|
does_abort(missing) -> true;
|
||||||
|
does_abort({switch, _, _, Alts, Def}) ->
|
||||||
|
lists:all(fun does_abort/1, [Def | Alts]);
|
||||||
|
does_abort(_) -> false.
|
||||||
|
|
||||||
%% STORE R A, SWITCH R --> SWITCH A
|
%% STORE R A, SWITCH R --> SWITCH A
|
||||||
r_inline_switch_target(Store = {i, _, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
|
r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
|
||||||
|
Ann1 =
|
||||||
|
case is_reg(A) of
|
||||||
|
true -> Ann#{ live_out := ordsets:add_element(A, maps:get(live_out, Ann)) };
|
||||||
|
false -> Ann
|
||||||
|
end,
|
||||||
|
Store = {i, Ann1, {'STORE', R, A}},
|
||||||
Switch = {switch, A, Type, Alts, Def},
|
Switch = {switch, A, Type, Alts, Def},
|
||||||
case R of
|
case R of
|
||||||
|
A -> false;
|
||||||
?a -> {[Switch], Code};
|
?a -> {[Switch], Code};
|
||||||
{var, _} ->
|
{var, _} ->
|
||||||
case lists:any(fun(Alt) -> live_in(R, Alt) end, [Def | Alts]) of
|
case lists:any(fun(Alt) -> live_in(R, Alt) end, [Def | Alts]) of
|
||||||
@ -1257,8 +1243,9 @@ r_inline_switch_target(Store = {i, _, {'STORE', R, A}}, [{switch, R, Type, Alts,
|
|||||||
r_inline_switch_target(_, _) -> false.
|
r_inline_switch_target(_, _) -> false.
|
||||||
|
|
||||||
%% Float switch-body to closest switch
|
%% Float switch-body to closest switch
|
||||||
r_float_switch_body(I = {i, _, _}, [switch_body | Code]) ->
|
r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
|
||||||
{[], [switch_body, I | Code]};
|
{J1, I1} = swap_instrs(I, J),
|
||||||
|
{[], [J1, I1 | Code]};
|
||||||
r_float_switch_body(_, _) -> false.
|
r_float_switch_body(_, _) -> false.
|
||||||
|
|
||||||
%% Inline stores
|
%% Inline stores
|
||||||
@ -1267,38 +1254,42 @@ r_inline_store({i, _, {'STORE', R, R}}, Code) ->
|
|||||||
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
r_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;
|
||||||
?i(_) -> true;
|
?i(_) -> true;
|
||||||
_ -> false
|
{store, _} -> true;
|
||||||
|
_ -> false
|
||||||
end,
|
end,
|
||||||
if Inline -> r_inline_store([I], R, A, Code);
|
if Inline -> r_inline_store([I], false, R, A, Code);
|
||||||
true -> false end;
|
true -> false end;
|
||||||
r_inline_store(_, _) -> false.
|
r_inline_store(_, _) -> false.
|
||||||
|
|
||||||
r_inline_store(Acc, R, A, [switch_body | Code]) ->
|
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
|
||||||
r_inline_store([switch_body | Acc], R, A, Code);
|
r_inline_store([I | Acc], Progress, R, A, Code);
|
||||||
r_inline_store(Acc, R, A, [{i, Ann, I} | Code]) ->
|
r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||||
#{ write := W, pure := Pure } = 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 not live_in(R, Ann) orelse not Pure orelse lists:member(W, [R, A]) of
|
case live_in(R, Ann) of
|
||||||
true -> false;
|
false -> false; %% No more reads of R
|
||||||
false ->
|
true ->
|
||||||
case op_view(I) of
|
{I1, Progress1} =
|
||||||
{Op, S, As} ->
|
case op_view(I) of
|
||||||
case lists:member(R, As) of
|
{Op, S, As} ->
|
||||||
true ->
|
case lists:member(R, As) of
|
||||||
Acc1 = [{i, Ann, from_op_view(Op, S, lists:map(Inl, As))} | Acc],
|
true -> {from_op_view(Op, S, lists:map(Inl, As)), true};
|
||||||
case r_inline_store(Acc1, R, A, Code) of
|
false -> {I, Progress}
|
||||||
false -> {lists:reverse(Acc1), Code};
|
end;
|
||||||
{_, _} = Res -> Res
|
_ -> {I, Progress}
|
||||||
end;
|
end,
|
||||||
false ->
|
Acc1 = [{i, Ann, I1} | Acc],
|
||||||
r_inline_store([{i, Ann, I} | Acc], R, A, Code)
|
%% Stop if write to R or A
|
||||||
end;
|
case lists:member(W, [R, A]) of
|
||||||
_ -> r_inline_store([{i, Ann, I} | Acc], R, A, Code)
|
true when Progress1 -> {lists:reverse(Acc1), Code};
|
||||||
|
true -> false;
|
||||||
|
false -> r_inline_store(Acc1, Progress1, R, A, Code)
|
||||||
end
|
end
|
||||||
end;
|
end;
|
||||||
r_inline_store(_Acc, _, _, _) -> false.
|
r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
|
||||||
|
r_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]) ->
|
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||||
@ -1320,8 +1311,9 @@ r_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
|
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
|
||||||
r_write_to_dead_var({i, Ann, I}, Code) ->
|
r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||||
|
#{ pure := Pure } = attributes(I),
|
||||||
case op_view(I) of
|
case op_view(I) of
|
||||||
{_Op, R = {var, _}, As} ->
|
{_Op, R, As} when R /= ?a, Pure ->
|
||||||
case live_out(R, Ann) of
|
case live_out(R, Ann) of
|
||||||
false ->
|
false ->
|
||||||
%% Subtle: we still have to pop the stack if any of the arguments
|
%% Subtle: we still have to pop the stack if any of the arguments
|
||||||
@ -1333,21 +1325,24 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
|
|||||||
end;
|
end;
|
||||||
r_write_to_dead_var(_, _) -> false.
|
r_write_to_dead_var(_, _) -> false.
|
||||||
|
|
||||||
|
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
|
||||||
op_view(T) when is_tuple(T) ->
|
op_view(T) when is_tuple(T) ->
|
||||||
case tuple_to_list(T) of
|
[Op, R | As] = tuple_to_list(T),
|
||||||
[Op, R | As] when ?IsOp(Op) ->
|
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
|
||||||
{Op, R, As};
|
case attributes(list_to_tuple([Op, dst | [src || _ <- As]])) of
|
||||||
_ -> false
|
#{ write := dst, read := Rs } -> CheckReads(Rs, {Op, R, As});
|
||||||
|
#{ write := none, read := Rs } -> CheckReads(Rs, {Op, none, [R | As]});
|
||||||
|
_ -> false
|
||||||
end;
|
end;
|
||||||
op_view(_) -> false.
|
op_view(_) -> false.
|
||||||
|
|
||||||
|
from_op_view(Op, none, As) -> list_to_tuple([Op | As]);
|
||||||
from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]).
|
from_op_view(Op, R, As) -> list_to_tuple([Op, R | As]).
|
||||||
|
|
||||||
%% Desugar and specialize and remove annotations
|
%% Desugar and specialize and remove annotations
|
||||||
-spec unannotate(scode_a()) -> scode();
|
-spec unannotate(scode_a()) -> scode();
|
||||||
(sinstr_a()) -> sinstr();
|
(sinstr_a()) -> sinstr();
|
||||||
(missing) -> missing.
|
(missing) -> missing.
|
||||||
unannotate(switch_body) -> [switch_body];
|
|
||||||
unannotate({switch, Arg, Type, Alts, Def}) ->
|
unannotate({switch, Arg, Type, Alts, Def}) ->
|
||||||
[{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}];
|
[{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}];
|
||||||
unannotate(missing) -> missing;
|
unannotate(missing) -> missing;
|
||||||
@ -1363,6 +1358,7 @@ desugar({'ADD', A, A, ?i(1)}) -> [aeb_fate_ops:inc(desugar_arg(A))];
|
|||||||
desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()];
|
desugar({'SUB', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:dec()];
|
||||||
desugar({'SUB', A, A, ?i(1)}) -> [aeb_fate_ops:dec(desugar_arg(A))];
|
desugar({'SUB', A, A, ?i(1)}) -> [aeb_fate_ops:dec(desugar_arg(A))];
|
||||||
desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(desugar_arg(A))];
|
desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(desugar_arg(A))];
|
||||||
|
desugar({'STORE', R, ?a}) -> [aeb_fate_ops:pop(desugar_arg(R))];
|
||||||
desugar({switch, Arg, Type, Alts, Def}) ->
|
desugar({switch, Arg, Type, Alts, Def}) ->
|
||||||
[{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}];
|
[{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}];
|
||||||
desugar(missing) -> missing;
|
desugar(missing) -> missing;
|
||||||
@ -1375,7 +1371,7 @@ desugar_args(I) when is_tuple(I) ->
|
|||||||
list_to_tuple([Op | lists:map(fun desugar_arg/1, Args)]);
|
list_to_tuple([Op | lists:map(fun desugar_arg/1, Args)]);
|
||||||
desugar_args(I) -> I.
|
desugar_args(I) -> I.
|
||||||
|
|
||||||
desugar_arg(?s) -> {var, -1};
|
desugar_arg({store, N}) -> {var, -N};
|
||||||
desugar_arg(A) -> A.
|
desugar_arg(A) -> A.
|
||||||
|
|
||||||
%% -- Phase III --------------------------------------------------------------
|
%% -- Phase III --------------------------------------------------------------
|
||||||
@ -1441,11 +1437,13 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
|||||||
{DefRef, DefBlk} =
|
{DefRef, DefBlk} =
|
||||||
case Default of
|
case Default of
|
||||||
missing when Catchall == none ->
|
missing when Catchall == none ->
|
||||||
FreshBlk([aeb_fate_ops:exit(?i(<<"Incomplete patterns">>))], none);
|
FreshBlk([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
|
||||||
missing -> {Catchall, []};
|
missing -> {Catchall, []};
|
||||||
_ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall)
|
_ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall)
|
||||||
%% ^ fall-through to the outer catchall
|
%% ^ fall-through to the outer catchall
|
||||||
end,
|
end,
|
||||||
|
%% 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} =
|
{Blk1, Code1, AltBlks} =
|
||||||
case Type of
|
case Type of
|
||||||
boolean ->
|
boolean ->
|
||||||
@ -1461,7 +1459,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
|||||||
_ -> FalseCode ++ [{jump, RestRef}]
|
_ -> FalseCode ++ [{jump, RestRef}]
|
||||||
end,
|
end,
|
||||||
case lists:usort(Alts) == [missing] of
|
case lists:usort(Alts) == [missing] of
|
||||||
true -> {Blk#blk{code = [{jump, DefRef}]}, [], []};
|
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
||||||
false ->
|
false ->
|
||||||
case Arg of
|
case Arg of
|
||||||
?i(false) -> {Blk#blk{code = ElseCode}, [], ThenBlk};
|
?i(false) -> {Blk#blk{code = ElseCode}, [], ThenBlk};
|
||||||
@ -1471,18 +1469,28 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
|||||||
end;
|
end;
|
||||||
tuple ->
|
tuple ->
|
||||||
[TCode] = Alts,
|
[TCode] = Alts,
|
||||||
{Blk#blk{code = TCode ++ [{jump, RestRef}]}, [], []};
|
case TCode of
|
||||||
|
missing -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
||||||
|
_ -> {Blk#blk{code = Pop ++ TCode ++ [{jump, RestRef}]}, [], []}
|
||||||
|
end;
|
||||||
{variant, [_]} ->
|
{variant, [_]} ->
|
||||||
%% [SINGLE_CON_SWITCH] Single constructor switches don't need a
|
%% [SINGLE_CON_SWITCH] Single constructor switches don't need a
|
||||||
%% switch instruction.
|
%% switch instruction.
|
||||||
[AltCode] = Alts,
|
[AltCode] = Alts,
|
||||||
{Blk#blk{code = AltCode ++ [{jump, RestRef}]}, [], []};
|
case AltCode of
|
||||||
|
missing -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
||||||
|
_ -> {Blk#blk{code = Pop ++ AltCode ++ [{jump, RestRef}]}, [], []}
|
||||||
|
end;
|
||||||
{variant, _Ar} ->
|
{variant, _Ar} ->
|
||||||
MkBlk = fun(missing) -> {DefRef, []};
|
case lists:usort(Alts) == [missing] of
|
||||||
(ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef)
|
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
||||||
end,
|
false ->
|
||||||
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
|
MkBlk = fun(missing) -> {DefRef, []};
|
||||||
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)}
|
(ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef)
|
||||||
|
end,
|
||||||
|
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
|
||||||
|
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)}
|
||||||
|
end
|
||||||
end,
|
end,
|
||||||
Blk2 = Blk1#blk{catchall = DefRef}, %% Update catchall ref
|
Blk2 = Blk1#blk{catchall = DefRef}, %% Update catchall ref
|
||||||
block(Blk2, Code1 ++ Acc, DefBlk ++ RestBlk ++ AltBlks ++ Blocks, BlockAcc);
|
block(Blk2, Code1 ++ Acc, DefBlk ++ RestBlk ++ AltBlks ++ Blocks, BlockAcc);
|
||||||
@ -1498,9 +1506,10 @@ optimize_blocks(Blocks) ->
|
|||||||
RBlockMap = maps:from_list(RBlocks),
|
RBlockMap = maps:from_list(RBlocks),
|
||||||
RBlocks1 = reorder_blocks(RBlocks, []),
|
RBlocks1 = reorder_blocks(RBlocks, []),
|
||||||
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
|
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
|
||||||
RBlocks3 = remove_dead_blocks(RBlocks2),
|
RBlocks3 = shortcut_jump_chains(RBlocks2),
|
||||||
RBlocks4 = [ {Ref, tweak_returns(Code)} || {Ref, Code} <- RBlocks3 ],
|
RBlocks4 = remove_dead_blocks(RBlocks3),
|
||||||
Rev(RBlocks4).
|
RBlocks5 = [ {Ref, tweak_returns(Code)} || {Ref, Code} <- RBlocks4 ],
|
||||||
|
Rev(RBlocks5).
|
||||||
|
|
||||||
%% Choose the next block based on the final jump.
|
%% Choose the next block based on the final jump.
|
||||||
reorder_blocks([], Acc) ->
|
reorder_blocks([], Acc) ->
|
||||||
@ -1536,6 +1545,21 @@ inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref ->
|
|||||||
end;
|
end;
|
||||||
inline_block(_, _, Code) -> Code.
|
inline_block(_, _, Code) -> Code.
|
||||||
|
|
||||||
|
%% Shortcut jumps to blocks with a single jump
|
||||||
|
shortcut_jump_chains(RBlocks) ->
|
||||||
|
Subst = lists:foldl(fun({L1, [{jump, L2}]}, Sub) ->
|
||||||
|
Sub#{ L1 => maps:get(L2, Sub, L2) };
|
||||||
|
(_, Sub) -> Sub end, #{}, RBlocks),
|
||||||
|
[ {Ref, update_labels(Subst, Code)} || {Ref, Code} <- RBlocks ].
|
||||||
|
|
||||||
|
update_labels(Sub, Ref) when is_reference(Ref) ->
|
||||||
|
maps:get(Ref, Sub, Ref);
|
||||||
|
update_labels(Sub, L) when is_list(L) ->
|
||||||
|
lists:map(fun(X) -> update_labels(Sub, X) end, L);
|
||||||
|
update_labels(Sub, T) when is_tuple(T) ->
|
||||||
|
list_to_tuple(update_labels(Sub, tuple_to_list(T)));
|
||||||
|
update_labels(_, X) -> X.
|
||||||
|
|
||||||
%% Remove unused blocks
|
%% Remove unused blocks
|
||||||
remove_dead_blocks(Blocks = [{Top, _} | _]) ->
|
remove_dead_blocks(Blocks = [{Top, _} | _]) ->
|
||||||
BlockMap = maps:from_list(Blocks),
|
BlockMap = maps:from_list(Blocks),
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{application, aesophia,
|
{application, aesophia,
|
||||||
[{description, "Contract Language for aeternity"},
|
[{description, "Contract Language for aeternity"},
|
||||||
{vsn, "4.0.0"},
|
{vsn, "4.1.0-rc1"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{applications,
|
{applications,
|
||||||
[kernel,
|
[kernel,
|
||||||
|
@ -12,6 +12,14 @@
|
|||||||
|
|
||||||
-include_lib("eunit/include/eunit.hrl").
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
run_test(Test) ->
|
||||||
|
TestFun = list_to_atom(lists:concat([Test, "_test_"])),
|
||||||
|
[ begin
|
||||||
|
io:format("~s\n", [Label]),
|
||||||
|
Fun()
|
||||||
|
end || {Label, Fun} <- ?MODULE:TestFun() ],
|
||||||
|
ok.
|
||||||
|
|
||||||
%% Very simply test compile the given contracts. Only basic checks
|
%% Very simply test compile the given contracts. Only basic checks
|
||||||
%% are made on the output, just that it is a binary which indicates
|
%% are made on the output, just that it is a binary which indicates
|
||||||
%% that the compilation worked.
|
%% that the compilation worked.
|
||||||
@ -702,3 +710,44 @@ failing_code_gen_contracts() ->
|
|||||||
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
"The state cannot contain functions in the AEVM. Use FATE if you need this.")
|
||||||
].
|
].
|
||||||
|
|
||||||
|
validation_test_() ->
|
||||||
|
[{"Validation fail: " ++ C1 ++ " /= " ++ C2,
|
||||||
|
fun() ->
|
||||||
|
Actual = case validate(C1, C2) of
|
||||||
|
{error, Errs} -> Errs;
|
||||||
|
ok -> #{}
|
||||||
|
end,
|
||||||
|
check_errors(Expect, Actual)
|
||||||
|
end} || {C1, C2, Expect} <- validation_fails()] ++
|
||||||
|
[{"Validation of " ++ C,
|
||||||
|
fun() ->
|
||||||
|
?assertEqual(ok, validate(C, C))
|
||||||
|
end} || C <- compilable_contracts()].
|
||||||
|
|
||||||
|
validation_fails() ->
|
||||||
|
[{"deadcode", "nodeadcode",
|
||||||
|
[<<"Data error:\n"
|
||||||
|
"Byte code does not match source code.\n"
|
||||||
|
"- Functions in the source code but not in the byte code:\n"
|
||||||
|
" .MyList.map2">>]},
|
||||||
|
{"validation_test1", "validation_test2",
|
||||||
|
[<<"Data error:\n"
|
||||||
|
"Byte code does not match source code.\n"
|
||||||
|
"- The implementation of the function code_fail is different.\n"
|
||||||
|
"- The attributes of the function attr_fail differ:\n"
|
||||||
|
" Byte code: payable\n"
|
||||||
|
" Source code: \n"
|
||||||
|
"- The type of the function type_fail differs:\n"
|
||||||
|
" Byte code: integer => integer\n"
|
||||||
|
" Source code: {tvar,0} => {tvar,0}">>]},
|
||||||
|
{"validation_test1", "validation_test3",
|
||||||
|
[<<"Data error:\n"
|
||||||
|
"Byte code contract is not payable, but source code contract is.">>]}].
|
||||||
|
|
||||||
|
validate(Contract1, Contract2) ->
|
||||||
|
ByteCode = #{ fate_code := FCode } = compile(fate, Contract1),
|
||||||
|
FCode1 = aeb_fate_code:serialize(aeb_fate_code:strip_init_function(FCode)),
|
||||||
|
Source = aeso_test_utils:read_contract(Contract2),
|
||||||
|
aeso_compiler:validate_byte_code(ByteCode#{ byte_code := FCode1 }, Source,
|
||||||
|
[{backend, fate}, {include, {file_system, [aeso_test_utils:contract_path()]}}]).
|
||||||
|
|
||||||
|
4
test/contracts/validation_test1.aes
Normal file
4
test/contracts/validation_test1.aes
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
contract ValidationTest =
|
||||||
|
payable entrypoint attr_fail() = ()
|
||||||
|
entrypoint type_fail(x : int) = x
|
||||||
|
entrypoint code_fail(x) = x + 1
|
4
test/contracts/validation_test2.aes
Normal file
4
test/contracts/validation_test2.aes
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
contract ValidationTest =
|
||||||
|
entrypoint attr_fail() = ()
|
||||||
|
entrypoint type_fail(x) = x
|
||||||
|
entrypoint code_fail(x) = x - 1
|
4
test/contracts/validation_test3.aes
Normal file
4
test/contracts/validation_test3.aes
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
payable contract ValidationTest =
|
||||||
|
payable entrypoint attr_fail() = ()
|
||||||
|
entrypoint type_fail(x : int) = x
|
||||||
|
entrypoint code_fail(x) = x + 1
|
Loading…
x
Reference in New Issue
Block a user