Compare commits

..

45 Commits

Author SHA1 Message Date
Hans Svensson 6ca63e4b40 Merge pull request #184 from aeternity/GH-181-prepare-4.1.0
Bump version to 4.1.0
2019-11-26 09:02:56 +01:00
Ulf Norell 08b6148223 Bump version to 4.1.0 2019-11-26 09:02:26 +01:00
Ulf Norell 8a47603b62 Merge pull request #182 from aeternity/GH-181-prepare-4.1.0-rc1
GH-181 Prepare 4.1.0-rc1
2019-11-25 12:16:03 +01:00
Ulf Norell d4c9d369b1 Remove aesophia_cli and aesophia_http stuff from change log 2019-11-25 12:07:05 +01:00
Ulf Norell 8984ecc32d Bump version numbers 2019-11-25 11:55:31 +01:00
Ulf Norell 025c837886 4.1.0-rc1 change log 2019-11-25 11:52:42 +01:00
Ulf Norell 06e6138de1 Merge release notes for 4.0.0 release candidates into 4.0.0 entry 2019-11-25 11:42:05 +01:00
Ulf Norell 7eb4423e70 Merge pull request #180 from aeternity/fate-optimization-fixes
Sophia FATE backend overhaul
2019-11-25 11:29:35 +01:00
Ulf Norell bd64260e37 Remove impossible case
h/t dialyzer
2019-11-25 10:42:37 +01:00
Ulf Norell 6380e04a97 Strip switches on variants with only catch-all 2019-11-19 16:39:01 +01:00
Ulf Norell 2be3c9194d Optimize switches with a single successful branch
Typical case: require(_, _)
2019-11-19 15:14:00 +01:00
Ulf Norell d0cfd9cbbe Export to_basic_blocks for tests 2019-11-19 13:11:06 +01:00
Ulf Norell 7f7f53e044 Fix issue in basic block generation 2019-11-19 13:10:56 +01:00
Ulf Norell 7d8a773d6a Fix type specs 2019-11-19 13:10:26 +01:00
Ulf Norell d3f5d7f5c5 Fix lost dependency when inlining switch target 2019-11-18 12:20:32 +01:00
Ulf Norell 0b474843f9 Protect against ill-typed code 2019-11-18 12:20:32 +01:00
Ulf Norell 1a628ab29f Fix bad annotations on switch-body 2019-11-18 12:20:32 +01:00
Ulf Norell 03ad1ad1dd Protect switch optimizations against ill-typed code 2019-11-18 12:20:32 +01:00
Ulf Norell bfcb9ab324 Annotate switch bodies 2019-11-18 12:20:32 +01:00
Ulf Norell 4cc88be296 Desugar STORE R a to POP R 2019-11-18 12:20:32 +01:00
Ulf Norell 505603ad71 More optimizations for impure instructions 2019-11-18 12:20:32 +01:00
Ulf Norell 2d7c860e3a Rewrite liveness analysis 2019-11-18 12:20:32 +01:00
Ulf Norell 4976e0402e Don't crash constant propagation on ill-typed code 2019-11-18 12:20:32 +01:00
Ulf Norell 0478df72fc Fix dependency analysis for loops 2019-11-18 12:20:32 +01:00
Ulf Norell 35b20800c9 Refactor argument inlining optimization 2019-11-18 12:20:32 +01:00
Ulf Norell d4c5c610ee Don't include stack and immediates in liveness annotations 2019-11-18 12:20:32 +01:00
Ulf Norell 6868bec3ed Fix bug in dependency analysis of GAS 2019-11-18 12:20:32 +01:00
Ulf Norell e5702c068c Impure == writes to the chain
Reading is ok
2019-11-18 12:20:31 +01:00
Ulf Norell a4b21063e3 Get rid of IsOp 2019-11-18 12:20:31 +01:00
Ulf Norell aca6b89fcf Store arguments are now separate from vars 2019-11-18 12:20:31 +01:00
Ulf Norell 13b196568b Handle reads from undefined variables in liveness analysis
Doesn't affect well-formed code, but makes testing easier.
2019-11-18 12:20:31 +01:00
Ulf Norell eba4f1c79c Call instructions read the function argument 2019-11-18 12:20:31 +01:00
Ulf Norell 1ca3018958 Don't run pretty printer if not pretty printing 2019-11-18 12:20:31 +01:00
Ulf Norell e6b5c5a526 Fix bug in short-cut for IS_NIL 2019-11-18 12:20:31 +01:00
Ulf Norell 47ad607dd5 Handle arbitrary store registers 2019-11-18 12:20:31 +01:00
Ulf Norell e8a54395bf Export optimize_fun for tests 2019-11-18 12:20:31 +01:00
Ulf Norell a87065c3a0 Merge pull request #177 from aeternity/GH-174-encode-decode-bits-lima
GH-174 Encode/decode bits. Now also for Lima
2019-11-18 12:19:47 +01:00
Ulf Norell 49f9ef955f Prefix format annotation for negative numbers 2019-11-18 12:16:04 +01:00
Ulf Norell f42353b300 Handle encoding/decoding bits
Fixes GH-174
2019-11-18 12:16:04 +01:00
Ulf Norell 5d23a76094 Merge pull request #173 from aeternity/GH-172-validate-byte-code
Add function to validate byte code against source code
2019-11-18 10:00:04 +01:00
Ulf Norell 878140e03c Add function to validate byte code against source code 2019-11-15 14:22:44 +01:00
Hans Svensson ac58eb4259 Merge pull request #171 from aeternity/GH-170-stdlib_in_escript
Add stdlib include handling when inside an escript
2019-11-11 11:40:29 +01:00
Hans Svensson 22b88bd393 Add stdlib include handling when inside an escript 2019-11-11 11:05:07 +01:00
Ulf Norell 83c3015899 Merge pull request #169 from aeternity/fix-illformed-lex-errors
Fix mangled lex errors
2019-10-21 09:00:43 +02:00
Ulf Norell ec9434fbfd Fix mangled lex errors 2019-10-21 08:53:32 +02:00
13 changed files with 535 additions and 305 deletions
+21 -34
View File
@@ -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] - 2019-11-26
### 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,
+1 -1
View File
@@ -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"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+90
View File
@@ -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").
@@ -507,6 +508,14 @@ icode_to_term(T = {map, KT, VT}, M) ->
#{}; #{};
_ -> throw({todo, M}) _ -> throw({todo, M})
end; end;
icode_to_term(word, {unop, 'bnot', A}) ->
bnot icode_to_term(word, A);
icode_to_term(word, {binop, 'bor', A, B}) ->
icode_to_term(word, A) bor icode_to_term(word, B);
icode_to_term(word, {binop, 'bsl', A, B}) ->
icode_to_term(word, B) bsl icode_to_term(word, A);
icode_to_term(word, {binop, 'band', A, B}) ->
icode_to_term(word, A) band icode_to_term(word, B);
icode_to_term(typerep, _) -> icode_to_term(typerep, _) ->
throw({todo, typerep}); throw({todo, typerep});
icode_to_term(T, V) -> icode_to_term(T, V) ->
@@ -558,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) ->
+283 -247
View File
@@ -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;
@@ -261,6 +194,18 @@ term_to_fate({tuple, As}) ->
term_to_fate({con, Ar, I, As}) -> term_to_fate({con, Ar, I, As}) ->
FateAs = [ term_to_fate(A) || A <- As ], FateAs = [ term_to_fate(A) || A <- As ],
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs)); aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
term_to_fate({builtin, bits_all, []}) ->
aeb_fate_data:make_bits(-1);
term_to_fate({builtin, bits_none, []}) ->
aeb_fate_data:make_bits(0);
term_to_fate({op, bits_set, [B, I]}) ->
{bits, N} = term_to_fate(B),
J = term_to_fate(I),
{bits, N bor (1 bsl J)};
term_to_fate({op, bits_clear, [B, I]}) ->
{bits, N} = term_to_fate(B),
J = term_to_fate(I),
{bits, N band bnot (1 bsl J)};
term_to_fate({builtin, map_empty, []}) -> term_to_fate({builtin, map_empty, []}) ->
aeb_fate_data:make_map(#{}); aeb_fate_data:make_map(#{});
term_to_fate({'let', _, {builtin, map_empty, []}, Set}) -> term_to_fate({'let', _, {builtin, map_empty, []}, Set}) ->
@@ -649,23 +594,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)
@@ -685,11 +630,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 /= []],
@@ -697,70 +640,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,
@@ -769,12 +709,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);
@@ -859,26 +800,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]);
@@ -890,10 +831,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' -> Impure(none, []);%% TODO 'AENS_UPDATE' -> Impure(none, []);%% TODO
@@ -908,15 +849,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),
@@ -938,8 +881,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.
@@ -951,17 +892,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 --
@@ -983,7 +923,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).
@@ -995,7 +935,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)
@@ -1025,6 +965,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)
]. ].
@@ -1047,8 +988,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} ->
@@ -1122,8 +1064,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} ->
@@ -1139,18 +1082,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;
@@ -1167,20 +1112,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) ->
@@ -1188,7 +1134,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
@@ -1200,7 +1146,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],
@@ -1217,7 +1163,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;
@@ -1226,10 +1172,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
@@ -1242,8 +1240,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
@@ -1252,38 +1251,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]) ->
@@ -1305,8 +1308,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
@@ -1318,21 +1322,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;
@@ -1348,6 +1355,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;
@@ -1360,7 +1368,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 --------------------------------------------------------------
@@ -1426,11 +1434,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 ->
@@ -1446,7 +1456,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};
@@ -1456,18 +1466,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);
@@ -1483,9 +1503,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) ->
@@ -1521,6 +1542,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),
+22 -4
View File
@@ -50,7 +50,8 @@ parse_and_scan(P, S, Opts) ->
set_current_file(proplists:get_value(src_file, Opts, no_file)), set_current_file(proplists:get_value(src_file, Opts, no_file)),
case aeso_scan:scan(S) of case aeso_scan:scan(S) of
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens); {ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
Error -> Error {error, {{Input, Pos}, _}} ->
{error, {Pos, scan_error, Input}}
end. end.
-dialyzer({nowarn_function, parse_error/1}). -dialyzer({nowarn_function, parse_error/1}).
@@ -60,8 +61,8 @@ parse_error(Err) ->
mk_p_err(Pos, Msg) -> mk_p_err(Pos, Msg) ->
aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)). aeso_errors:new(parse_error, mk_pos(Pos), lists:flatten(Msg)).
mk_error({Pos, ScanE}) when ScanE == scan_error; ScanE == scan_error_no_state -> mk_error({Pos, scan_error, Input}) ->
mk_p_err(Pos, "Scan error\n"); mk_p_err(Pos, io_lib:format("Lexical error on input: ~s\n", [Input]));
mk_error({Pos, parse_error, Err}) -> mk_error({Pos, parse_error, Err}) ->
Msg = io_lib:format("~s\n", [Err]), Msg = io_lib:format("~s\n", [Err]),
mk_p_err(Pos, Msg); mk_p_err(Pos, Msg);
@@ -618,11 +619,28 @@ read_file(File, Opts) ->
case maps:get(binary_to_list(File), Files, not_found) of case maps:get(binary_to_list(File), Files, not_found) of
not_found -> {error, not_found}; not_found -> {error, not_found};
Src -> {ok, Src} Src -> {ok, Src}
end;
escript ->
try
Escript = escript:script_name(),
{ok, Sections} = escript:extract(Escript, []),
Archive = proplists:get_value(archive, Sections),
FileName = binary_to_list(filename:join([aesophia, priv, stdlib, File])),
case zip:extract(Archive, [{file_list, [FileName]}, memory]) of
{ok, [{_, Src}]} -> {ok, Src};
_ -> {error, not_found}
end
catch _:_ ->
{error, not_found}
end end
end. end.
stdlib_options() -> stdlib_options() ->
[{include, {file_system, [aeso_stdlib:stdlib_include_path()]}}]. StdLibDir = aeso_stdlib:stdlib_include_path(),
case filelib:is_dir(StdLibDir) of
true -> [{include, {file_system, [StdLibDir]}}];
false -> [{include, escript}]
end.
get_include_code(File, Ann, Opts) -> get_include_code(File, Ann, Opts) ->
case {read_file(File, Opts), read_file(File, stdlib_options())} of case {read_file(File, Opts), read_file(File, stdlib_options())} of
+27 -5
View File
@@ -18,9 +18,14 @@ from_aevm(word, {id, _, "address"}, N) -> address_literal(ac
from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N); from_aevm(word, {app_t, _, {id, _, "oracle"}, _}, N) -> address_literal(oracle_pubkey, N);
from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N); from_aevm(word, {app_t, _, {id, _, "oracle_query"}, _}, N) -> address_literal(oracle_query_id, N);
from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N); from_aevm(word, {con, _, _Name}, N) -> address_literal(contract_pubkey, N);
from_aevm(word, {id, _, "int"}, N) -> <<N1:256/signed>> = <<N:256>>, {int, [], N1}; from_aevm(word, {id, _, "int"}, N0) ->
from_aevm(word, {id, _, "bits"}, N) -> error({todo, bits, N}); <<N:256/signed>> = <<N0:256>>,
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0}; if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_aevm(word, {id, _, "bits"}, N0) ->
<<N:256/signed>> = <<N0:256>>,
make_bits(N);
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 -> from_aevm(word, {bytes_t, _, Len}, Val) when Len =< 32 ->
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>, <<Bytes:Len/unit:8, _/binary>> = <<Val:32/unit:8>>,
{bytes, [], <<Bytes:Len/unit:8>>}; {bytes, [], <<Bytes:Len/unit:8>>};
@@ -55,6 +60,7 @@ from_aevm({variant, VmCons}, {variant_t, Cons}, {variant, Tag, Args})
VmTypes = lists:nth(Tag + 1, VmCons), VmTypes = lists:nth(Tag + 1, VmCons),
ConType = lists:nth(Tag + 1, Cons), ConType = lists:nth(Tag + 1, Cons),
from_aevm(VmTypes, ConType, Args); from_aevm(VmTypes, ConType, Args);
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args) from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
when length(VmTypes) == length(Types), when length(VmTypes) == length(Types),
length(VmTypes) == length(Args) -> length(VmTypes) == length(Args) ->
@@ -70,8 +76,10 @@ from_fate({app_t, _, {id, _, "oracle"}, _}, ?FATE_ORACLE(Bin)) -> {oracle_pubkey
from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin}; from_fate({app_t, _, {id, _, "oracle_query"}, _}, ?FATE_ORACLE_Q(Bin)) -> {oracle_query_id, [], Bin};
from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin}; from_fate({con, _, _Name}, ?FATE_CONTRACT(Bin)) -> {contract_pubkey, [], Bin};
from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin}; from_fate({bytes_t, _, N}, ?FATE_BYTES(Bin)) when byte_size(Bin) == N -> {bytes, [], Bin};
from_fate({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin}); from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(N);
from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N}; from_fate({id, _, "int"}, N) when is_integer(N) ->
if N < 0 -> {app, [{format, prefix}], {'-', []}, [{int, [], -N}]};
true -> {int, [], N} end;
from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B}; from_fate({id, _, "bool"}, B) when is_boolean(B) -> {bool, [], B};
from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S}; from_fate({id, _, "string"}, S) when is_binary(S) -> {string, [], S};
from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) -> from_fate({app_t, _, {id, _, "list"}, [Type]}, List) when is_list(List) ->
@@ -105,9 +113,23 @@ from_fate({variant_t, Cons}, {variant, Ar, Tag, Args})
from_fate(ConType, ArgList); from_fate(ConType, ArgList);
_ -> throw(cannot_translate_to_sophia) _ -> throw(cannot_translate_to_sophia)
end; end;
from_fate({constr_t, _, Con, []}, []) -> Con;
from_fate({constr_t, _, Con, Types}, Args) from_fate({constr_t, _, Con, Types}, Args)
when length(Types) == length(Args) -> when length(Types) == length(Args) ->
{app, [], Con, [ from_fate(Type, Arg) {app, [], Con, [ from_fate(Type, Arg)
|| {Type, Arg} <- lists:zip(Types, Args) ]}; || {Type, Arg} <- lists:zip(Types, Args) ]};
from_fate(_Type, _Data) -> from_fate(_Type, _Data) ->
throw(cannot_translate_to_sophia). throw(cannot_translate_to_sophia).
make_bits(N) ->
Id = fun(F) -> {qid, [], ["Bits", F]} end,
if N < 0 -> make_bits(Id("clear"), Id("all"), 0, bnot N);
true -> make_bits(Id("set"), Id("none"), 0, N) end.
make_bits(_Set, Zero, _I, 0) -> Zero;
make_bits(Set, Zero, I, N) when 0 == N rem 2 ->
make_bits(Set, Zero, I + 1, N div 2);
make_bits(Set, Zero, I, N) ->
{app, [], Set, [make_bits(Set, Zero, I + 1, N div 2), {int, [], I}]}.
+1 -1
View File
@@ -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"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+1 -1
View File
@@ -62,7 +62,7 @@ encode_decode_sophia_test() ->
Other -> Other Other -> Other
end end, end end,
ok = Check("int", "42"), ok = Check("int", "42"),
ok = Check("int", "-42"), ok = Check("int", "- 42"),
ok = Check("bool", "true"), ok = Check("bool", "true"),
ok = Check("bool", "false"), ok = Check("bool", "false"),
ok = Check("string", "\"Hello\""), ok = Check("string", "\"Hello\""),
+28 -12
View File
@@ -29,11 +29,10 @@ calldata_test_() ->
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]); true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
false -> undefined false -> undefined
end, end,
case FateExprs == undefined orelse AevmExprs == undefined of ParsedExprs = parse_args(Fun, Args),
true -> ok; [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
false -> [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(FateExprs, AevmExprs) ok
end
end} || {ContractName, Fun, Args} <- compilable_contracts()]. end} || {ContractName, Fun, Args} <- compilable_contracts()].
calldata_aci_test_() -> calldata_aci_test_() ->
@@ -53,19 +52,34 @@ calldata_aci_test_() ->
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]); true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
false -> undefined false -> undefined
end, end,
case FateExprs == undefined orelse AevmExprs == undefined of ParsedExprs = parse_args(Fun, Args),
true -> ok; [ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
false -> [ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
?assertEqual(FateExprs, AevmExprs) ok
end
end} || {ContractName, Fun, Args} <- compilable_contracts()]. end} || {ContractName, Fun, Args} <- compilable_contracts()].
parse_args(Fun, Args) ->
[{contract, _, _, [{letfun, _, _, _, _, {app, _, _, AST}}]}] =
aeso_parser:string("contract Temp = function foo() = " ++ Fun ++ "(" ++ string:join(Args, ", ") ++ ")"),
strip_ann(AST).
strip_ann(T) when is_tuple(T) ->
strip_ann1(setelement(2, T, []));
strip_ann(X) -> strip_ann1(X).
strip_ann1({map, [], KVs}) ->
{map, [], [{strip_ann(K), strip_ann(V)} || {K, V} <- KVs]};
strip_ann1(T) when is_tuple(T) ->
list_to_tuple(strip_ann1(tuple_to_list(T)));
strip_ann1(L) when is_list(L) ->
lists:map(fun strip_ann/1, L);
strip_ann1(X) -> X.
ast_exprs(ContractString, Fun, Args, Opts) -> ast_exprs(ContractString, Fun, Args, Opts) ->
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)), {ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)), {ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
?assert(is_list(Exprs)), ?assert(is_list(Exprs)),
Exprs. strip_ann(Exprs).
check_errors(Expect, ErrorString) -> check_errors(Expect, ErrorString) ->
%% This removes the final single \n as well. %% This removes the final single \n as well.
@@ -85,7 +99,9 @@ compilable_contracts() ->
{"maps", "init", []}, {"maps", "init", []},
{"funargs", "menot", ["false"]}, {"funargs", "menot", ["false"]},
{"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]}, {"funargs", "append", ["[\"false\", \" is\", \" not\", \" true\"]"]},
%% TODO {"funargs", "bitsum", ["Bits.all"]}, {"funargs", "bitsum", ["Bits.all"]},
{"funargs", "bitsum", ["Bits.clear(Bits.clear(Bits.all, 4), 2)"]}, %% Order matters for test
{"funargs", "bitsum", ["Bits.set(Bits.set(Bits.none, 4), 2)"]},
{"funargs", "read", ["{label = \"question 1\", result = 4}"]}, {"funargs", "read", ["{label = \"question 1\", result = 4}"]},
{"funargs", "sjutton", ["#0011012003100011012003100011012003"]}, {"funargs", "sjutton", ["#0011012003100011012003100011012003"]},
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940" {"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
+49
View File
@@ -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
View File
@@ -0,0 +1,4 @@
contract ValidationTest =
payable entrypoint attr_fail() = ()
entrypoint type_fail(x : int) = x
entrypoint code_fail(x) = x + 1
+4
View File
@@ -0,0 +1,4 @@
contract ValidationTest =
entrypoint attr_fail() = ()
entrypoint type_fail(x) = x
entrypoint code_fail(x) = x - 1
+4
View File
@@ -0,0 +1,4 @@
payable contract ValidationTest =
payable entrypoint attr_fail() = ()
entrypoint type_fail(x : int) = x
entrypoint code_fail(x) = x + 1