Compare commits
57 Commits
v4.0.0-rc5
...
v4.1.0-rc1
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a47603b62 | |||
| d4c9d369b1 | |||
| 8984ecc32d | |||
| 025c837886 | |||
| 06e6138de1 | |||
| 7eb4423e70 | |||
| bd64260e37 | |||
| 6380e04a97 | |||
| 2be3c9194d | |||
| d0cfd9cbbe | |||
| 7f7f53e044 | |||
| 7d8a773d6a | |||
| d3f5d7f5c5 | |||
| 0b474843f9 | |||
| 1a628ab29f | |||
| 03ad1ad1dd | |||
| bfcb9ab324 | |||
| 4cc88be296 | |||
| 505603ad71 | |||
| 2d7c860e3a | |||
| 4976e0402e | |||
| 0478df72fc | |||
| 35b20800c9 | |||
| d4c5c610ee | |||
| 6868bec3ed | |||
| e5702c068c | |||
| a4b21063e3 | |||
| aca6b89fcf | |||
| 13b196568b | |||
| eba4f1c79c | |||
| 1ca3018958 | |||
| e6b5c5a526 | |||
| 47ad607dd5 | |||
| e8a54395bf | |||
| a87065c3a0 | |||
| 49f9ef955f | |||
| f42353b300 | |||
| 5d23a76094 | |||
| 878140e03c | |||
| ac58eb4259 | |||
| 22b88bd393 | |||
| 83c3015899 | |||
| ec9434fbfd | |||
| b81312a714 | |||
| 63c0b714d0 | |||
| d018cc5819 | |||
| f5b2732b04 | |||
| f86f7984f4 | |||
| 1ae0a42071 | |||
| 18ae801333 | |||
| 32d52f0abc | |||
| 5e6ff6c9a7 | |||
| 2d6d506d63 | |||
| 482d22d46b | |||
| a333888fb9 | |||
| 5fc6e18cd2 | |||
| dd94a6bd67 |
+22
-24
@@ -9,41 +9,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Changed
|
||||
### Removed
|
||||
|
||||
## [4.0.0-rc5] - 2019-09-27
|
||||
## [4.1.0-rc1] - 2019-11-25
|
||||
### Added
|
||||
- Support encoding and decoding bit fields in call arguments and results.
|
||||
### Changed
|
||||
- Bug fixes in error reporting.
|
||||
- Bug fix in variable liveness analysis for FATE.
|
||||
- Various improvements to FATE code generator.
|
||||
### Removed
|
||||
|
||||
## [4.0.0-rc4] - 2019-09-13
|
||||
## [4.0.0] - 2019-10-11
|
||||
### Added
|
||||
- `Address.to_contract` - casts an address to a (any) contract type.
|
||||
- Pragma to check compiler version, e.g. `@compiler >= 4.0`.
|
||||
- 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
|
||||
(de-)construct byte arrays.
|
||||
- `[a..b]` language construct, returning the list of numbers between
|
||||
`a` and `b` (inclusive). Returns the empty list if `a` > `b`.
|
||||
- [Standard libraries] (https://github.com/aeternity/protocol/blob/master/contracts/sophia_stdlib.md)
|
||||
- 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
|
||||
of the APIs now take `{backend, aevm | fate}` to decide wich backend to produce artifacts
|
||||
for.
|
||||
@@ -60,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)`
|
||||
can be used to check if an (contract) address is payable or not.
|
||||
### 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
|
||||
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,
|
||||
@@ -167,7 +164,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Simplify calldata creation - instead of passing a compiled contract, simply
|
||||
pass a (stubbed) contract string.
|
||||
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc5...HEAD
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0...HEAD
|
||||
[4.0.0]: https://github.com/aeternity/aesophia/compare/v4.0.0...v3.2.0
|
||||
[4.0.0-rc5]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc4...v4.0.0-rc5
|
||||
[4.0.0-rc4]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc3...v4.0.0-rc4
|
||||
[4.0.0-rc3]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc1...v4.0.0-rc3
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"a66dc0a"}}}
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {ref,"4f4d6d3"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git",
|
||||
@@ -15,7 +15,7 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "4.0.0-rc5"},
|
||||
{relx, [{release, {aesophia, "4.1.0-rc1"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{"1.1.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"a66dc0a97facdeaad7e5403018ad195d989e4793"}},
|
||||
{ref,"4f4d6d30cd2c46b3830454d650a424d513f69134"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
|
||||
+108
-34
@@ -60,7 +60,8 @@
|
||||
|
||||
-record(is_contract_constraint,
|
||||
{ contract_t :: utype(),
|
||||
context :: aeso_syntax:expr() %% The address literal
|
||||
context :: {contract_literal, aeso_syntax:expr()} |
|
||||
{address_to_contract, aeso_syntax:ann()}
|
||||
}).
|
||||
|
||||
-type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
|
||||
@@ -83,7 +84,7 @@
|
||||
-type qname() :: [string()].
|
||||
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}.
|
||||
|
||||
-type type_constraints() :: none | bytes_concat | bytes_split.
|
||||
-type type_constraints() :: none | bytes_concat | bytes_split | address_to_contract.
|
||||
|
||||
-type fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
|
||||
-type type_info() :: {aeso_syntax:ann(), typedef()}.
|
||||
@@ -110,7 +111,7 @@
|
||||
, in_pattern = false :: boolean()
|
||||
, stateful = false :: boolean()
|
||||
, current_function = none :: none | aeso_syntax:id()
|
||||
, what = top :: top | namespace | contract
|
||||
, what = top :: top | namespace | contract | main_contract
|
||||
}).
|
||||
|
||||
-type env() :: #env{}.
|
||||
@@ -175,12 +176,13 @@ bind_fun(X, Type, Env) ->
|
||||
end.
|
||||
|
||||
-spec force_bind_fun(name(), type() | typesig(), env()) -> env().
|
||||
force_bind_fun(X, Type, Env) ->
|
||||
force_bind_fun(X, Type, Env = #env{ what = What }) ->
|
||||
Ann = aeso_syntax:get_ann(Type),
|
||||
NoCode = get_option(no_code, false),
|
||||
Entry = case X == "init" andalso Env#env.what == contract andalso not NoCode of
|
||||
true -> {reserved_init, Ann, Type};
|
||||
false -> {Ann, Type}
|
||||
Entry = if X == "init", What == main_contract, not NoCode ->
|
||||
{reserved_init, Ann, Type};
|
||||
What == contract -> {contract_fun, Ann, Type};
|
||||
true -> {Ann, Type}
|
||||
end,
|
||||
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
|
||||
Scope#scope{ funs = [{X, Entry} | Funs] }
|
||||
@@ -306,6 +308,9 @@ lookup_env1(#env{ namespace = Current, scopes = Scopes }, Kind, Ann, QName) ->
|
||||
{reserved_init, Ann1, Type} ->
|
||||
type_error({cannot_call_init_function, Ann}),
|
||||
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error
|
||||
{contract_fun, Ann1, Type} ->
|
||||
type_error({contract_treated_as_namespace, Ann, QName}),
|
||||
{QName, {Ann1, Type}};
|
||||
{Ann1, _} = E ->
|
||||
%% Check that it's not private (or we can see private funs)
|
||||
case not is_private(Ann1) orelse AllowPrivate of
|
||||
@@ -519,6 +524,7 @@ global_env() ->
|
||||
%% Conversion
|
||||
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
|
||||
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)},
|
||||
{"to_contract", FunC(address_to_contract, [Address], A)},
|
||||
{"is_oracle", Fun1(Address, Bool)},
|
||||
{"is_contract", Fun1(Address, Bool)},
|
||||
{"is_payable", Fun1(Address, Bool)}]) },
|
||||
@@ -582,7 +588,8 @@ infer1(Env, [], Acc, _Options) -> {Env, lists:reverse(Acc)};
|
||||
infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
|
||||
%% do type inference on each contract independently.
|
||||
check_scope_name_clash(Env, contract, ConName),
|
||||
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), contract, Code, Options),
|
||||
What = if Rest == [] -> main_contract; true -> contract end,
|
||||
{Env1, Code1} = infer_contract_top(push_scope(contract, ConName, Env), What, Code, Options),
|
||||
Contract1 = {contract, Ann, ConName, Code1},
|
||||
Env2 = pop_scope(Env1),
|
||||
Env3 = bind_contract(Contract1, Env2),
|
||||
@@ -591,7 +598,10 @@ infer1(Env, [{namespace, Ann, Name, Code} | Rest], Acc, Options) ->
|
||||
check_scope_name_clash(Env, namespace, Name),
|
||||
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options),
|
||||
Namespace1 = {namespace, Ann, Name, Code1},
|
||||
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options).
|
||||
infer1(pop_scope(Env1), Rest, [Namespace1 | Acc], Options);
|
||||
infer1(Env, [{pragma, _, _} | Rest], Acc, Options) ->
|
||||
%% Pragmas are checked in check_modifiers
|
||||
infer1(Env, Rest, Acc, Options).
|
||||
|
||||
check_scope_name_clash(Env, Kind, Name) ->
|
||||
case get_scope(Env, qname(Name)) of
|
||||
@@ -602,7 +612,7 @@ check_scope_name_clash(Env, Kind, Name) ->
|
||||
destroy_and_report_type_errors(Env)
|
||||
end.
|
||||
|
||||
-spec infer_contract_top(env(), contract | namespace, [aeso_syntax:decl()], list(option())) ->
|
||||
-spec infer_contract_top(env(), main_contract | contract | namespace, [aeso_syntax:decl()], list(option())) ->
|
||||
{env(), [aeso_syntax:decl()]}.
|
||||
infer_contract_top(Env, Kind, Defs0, _Options) ->
|
||||
Defs = desugar(Defs0),
|
||||
@@ -610,7 +620,7 @@ infer_contract_top(Env, Kind, Defs0, _Options) ->
|
||||
|
||||
%% infer_contract takes a proplist mapping global names to types, and
|
||||
%% a list of definitions.
|
||||
-spec infer_contract(env(), contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
|
||||
-spec infer_contract(env(), main_contract | contract | namespace, [aeso_syntax:decl()]) -> {env(), [aeso_syntax:decl()]}.
|
||||
infer_contract(Env0, What, Defs) ->
|
||||
Env = Env0#env{ what = What },
|
||||
Kind = fun({type_def, _, _, _, _}) -> type;
|
||||
@@ -624,8 +634,9 @@ infer_contract(Env0, What, Defs) ->
|
||||
check_unexpected(Get(unexpected)),
|
||||
Env2 =
|
||||
case What of
|
||||
namespace -> Env1;
|
||||
contract -> bind_state(Env1) %% bind state and put
|
||||
namespace -> Env1;
|
||||
contract -> Env1;
|
||||
main_contract -> bind_state(Env1) %% bind state and put
|
||||
end,
|
||||
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]),
|
||||
Env3 = bind_funs(ProtoSigs, Env2),
|
||||
@@ -704,19 +715,46 @@ check_unexpected(Xs) ->
|
||||
|
||||
check_modifiers(Env, Contracts) ->
|
||||
create_type_errors(),
|
||||
[ case C of
|
||||
{contract, _, Con, Decls} ->
|
||||
check_modifiers1(contract, Decls),
|
||||
case {lists:keymember(letfun, 1, Decls),
|
||||
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
|
||||
{true, []} -> type_error({contract_has_no_entrypoints, Con});
|
||||
_ -> ok
|
||||
end;
|
||||
{namespace, _, _, Decls} -> check_modifiers1(namespace, Decls);
|
||||
Decl -> type_error({bad_top_level_decl, Decl})
|
||||
end || C <- Contracts ],
|
||||
check_modifiers_(Env, Contracts),
|
||||
destroy_and_report_type_errors(Env).
|
||||
|
||||
check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) ->
|
||||
IsMain = Rest == [],
|
||||
check_modifiers1(contract, Decls),
|
||||
case {lists:keymember(letfun, 1, Decls),
|
||||
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
|
||||
{true, []} -> type_error({contract_has_no_entrypoints, Con});
|
||||
_ when not IsMain ->
|
||||
case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of
|
||||
[{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id});
|
||||
[] -> ok
|
||||
end;
|
||||
_ -> ok
|
||||
end,
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [{namespace, _, _, Decls} | Rest]) ->
|
||||
check_modifiers1(namespace, Decls),
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [{pragma, Ann, Pragma} | Rest]) ->
|
||||
check_pragma(Env, Ann, Pragma),
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(Env, [Decl | Rest]) ->
|
||||
type_error({bad_top_level_decl, Decl}),
|
||||
check_modifiers_(Env, Rest);
|
||||
check_modifiers_(_Env, []) -> ok.
|
||||
|
||||
-spec check_pragma(env(), aeso_syntax:ann(), aeso_syntax:pragma()) -> ok.
|
||||
check_pragma(_Env, Ann, {compiler, Op, Ver}) ->
|
||||
case aeso_compiler:numeric_version() of
|
||||
{error, Err} -> type_error({failed_to_get_compiler_version, Err});
|
||||
{ok, Version} ->
|
||||
Strip = fun(V) -> lists:reverse(lists:dropwhile(fun(X) -> X == 0 end, lists:reverse(V))) end,
|
||||
case apply(erlang, Op, [Strip(Version), Strip(Ver)]) of
|
||||
true -> ok;
|
||||
false -> type_error({compiler_version_mismatch, Ann, Version, Op, Ver})
|
||||
end
|
||||
end.
|
||||
|
||||
-spec check_modifiers1(contract | namespace, [aeso_syntax:decl()] | aeso_syntax:decl()) -> ok.
|
||||
check_modifiers1(What, Decls) when is_list(Decls) ->
|
||||
_ = [ check_modifiers1(What, Decl) || Decl <- Decls ],
|
||||
@@ -1081,7 +1119,7 @@ infer_expr(_Env, Body={oracle_query_id, As, _}) ->
|
||||
infer_expr(_Env, Body={contract_pubkey, As, _}) ->
|
||||
Con = fresh_uvar(As),
|
||||
constrain([#is_contract_constraint{ contract_t = Con,
|
||||
context = Body }]),
|
||||
context = {contract_literal, Body} }]),
|
||||
{typed, As, Body, Con};
|
||||
infer_expr(_Env, Body={id, As, "_"}) ->
|
||||
{typed, As, Body, fresh_uvar(As)};
|
||||
@@ -1653,11 +1691,11 @@ check_record_create_constraints(Env, [C | Cs]) ->
|
||||
|
||||
check_is_contract_constraints(_Env, []) -> ok;
|
||||
check_is_contract_constraints(Env, [C | Cs]) ->
|
||||
#is_contract_constraint{ contract_t = Type, context = Lit } = C,
|
||||
#is_contract_constraint{ contract_t = Type, context = Cxt } = C,
|
||||
Type1 = unfold_types_in_type(Env, instantiate(Type)),
|
||||
case lookup_type(Env, record_type_name(Type1)) of
|
||||
{_, {_Ann, {[], {contract_t, _}}}} -> ok;
|
||||
_ -> type_error({not_a_contract_type, Type1, Lit})
|
||||
_ -> type_error({not_a_contract_type, Type1, Cxt})
|
||||
end,
|
||||
check_is_contract_constraints(Env, Cs).
|
||||
|
||||
@@ -1747,7 +1785,7 @@ solve_known_record_types(Env, Constraints) ->
|
||||
C
|
||||
end;
|
||||
_ ->
|
||||
type_error({not_a_record_type, RecType, When}),
|
||||
type_error({not_a_record_type, instantiate(RecType), When}),
|
||||
not_solved
|
||||
end
|
||||
end
|
||||
@@ -2078,6 +2116,9 @@ freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) ->
|
||||
FunT.
|
||||
|
||||
apply_typesig_constraint(_Ann, none, _FunT) -> ok;
|
||||
apply_typesig_constraint(Ann, address_to_contract, {fun_t, _, [], [_], Type}) ->
|
||||
constrain([#is_contract_constraint{ contract_t = Type,
|
||||
context = {address_to_contract, Ann}}]);
|
||||
apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
|
||||
add_bytes_constraint({add_bytes, Ann, concat, A, B, C});
|
||||
apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
|
||||
@@ -2176,12 +2217,28 @@ mk_error({not_a_record_type, Type, Why}) ->
|
||||
Msg = io_lib:format("~s\n", [pp_type("Not a record type: ", Type)]),
|
||||
{Pos, Ctxt} = pp_why_record(Why),
|
||||
mk_t_err(Pos, Msg, Ctxt);
|
||||
mk_error({not_a_contract_type, Type, Lit}) ->
|
||||
Msg = io_lib:format("The type ~s is not a contract type\n"
|
||||
"when checking that the contract literal at ~s\n~s\n"
|
||||
"has the type\n~s\n",
|
||||
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]),
|
||||
mk_t_err(pos(Lit), Msg);
|
||||
mk_error({not_a_contract_type, Type, Cxt}) ->
|
||||
Msg =
|
||||
case Type of
|
||||
{tvar, _, _} ->
|
||||
"Unresolved contract type\n";
|
||||
_ ->
|
||||
io_lib:format("The type ~s is not a contract type\n", [pp_type("", Type)])
|
||||
end,
|
||||
{Pos, Cxt1} =
|
||||
case Cxt of
|
||||
{contract_literal, Lit} ->
|
||||
{pos(Lit),
|
||||
io_lib:format("when checking that the contract literal\n~s\n"
|
||||
"has the type\n~s\n",
|
||||
[pp_expr(" ", Lit), pp_type(" ", Type)])};
|
||||
{address_to_contract, Ann} ->
|
||||
{pos(Ann),
|
||||
io_lib:format("when checking that the call to\n Address.to_contract\n"
|
||||
"has the type\n~s\n",
|
||||
[pp_type(" ", Type)])}
|
||||
end,
|
||||
mk_t_err(Pos, Msg, Cxt1);
|
||||
mk_error({non_linear_pattern, Pattern, Nonlinear}) ->
|
||||
Msg = io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
|
||||
[plural("", "s", Nonlinear), string:join(Nonlinear, ", "),
|
||||
@@ -2343,6 +2400,10 @@ mk_error({contract_has_no_entrypoints, Con}) ->
|
||||
"contract functions must be declared with the 'entrypoint' keyword instead of\n"
|
||||
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
|
||||
mk_t_err(pos(Con), Msg);
|
||||
mk_error({definition_in_non_main_contract, Ann, {id, _, Id}}) ->
|
||||
Msg = "Only the main contract can contain defined functions or entrypoints.\n",
|
||||
Cxt = io_lib:format("Fix: replace the definition of '~s' by a type signature.\n", [Id]),
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error({unbound_type, Type}) ->
|
||||
Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]),
|
||||
mk_t_err(pos(Type), Msg);
|
||||
@@ -2358,6 +2419,10 @@ mk_error({cannot_call_init_function, Ann}) ->
|
||||
Msg = "The 'init' function is called exclusively by the create contract transaction\n"
|
||||
"and cannot be called from the contract code.\n",
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({contract_treated_as_namespace, Ann, [Con, Fun] = QName}) ->
|
||||
Msg = io_lib:format("Invalid call to contract entrypoint '~s'.\n", [string:join(QName, ".")]),
|
||||
Cxt = io_lib:format("It must be called as 'c.~s' for some c : ~s.\n", [Fun, Con]),
|
||||
mk_t_err(pos(Ann), Msg, Cxt);
|
||||
mk_error({bad_top_level_decl, Decl}) ->
|
||||
What = case element(1, Decl) of
|
||||
letval -> "function or entrypoint";
|
||||
@@ -2382,6 +2447,15 @@ mk_error({unsolved_bytes_constraint, Ann, split, A, B, C}) ->
|
||||
[ pp_type(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A),
|
||||
pp_type(" - ", B), pp_loc(B)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error({failed_to_get_compiler_version, Err}) ->
|
||||
Msg = io_lib:format("Failed to get compiler version. Error:\n ~p\n", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg);
|
||||
mk_error({compiler_version_mismatch, Ann, Version, Op, Bound}) ->
|
||||
PrintV = fun(V) -> string:join([integer_to_list(N) || N <- V], ".") end,
|
||||
Msg = io_lib:format("Cannot compile with this version of the compiler,\n"
|
||||
"because it does not satisfy the constraint"
|
||||
" ~s ~s ~s\n", [PrintV(Version), Op, PrintV(Bound)]),
|
||||
mk_t_err(pos(Ann), Msg);
|
||||
mk_error(Err) ->
|
||||
Msg = io_lib:format("Unknown error: ~p\n", [Err]),
|
||||
mk_t_err(pos(0, 0), Msg).
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
map_delete | map_member | map_size | string_length |
|
||||
string_concat | bits_set | bits_clear | bits_test | bits_sum |
|
||||
bits_intersection | bits_union | bits_difference |
|
||||
contract_to_address | crypto_verify_sig | crypto_verify_sig_secp256k1 |
|
||||
contract_to_address | address_to_contract | crypto_verify_sig | crypto_verify_sig_secp256k1 |
|
||||
crypto_sha3 | crypto_sha256 | crypto_blake2b |
|
||||
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1.
|
||||
|
||||
@@ -200,7 +200,7 @@ builtins() ->
|
||||
{"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]},
|
||||
{["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]},
|
||||
{["Int"], [{"to_str", 1}]},
|
||||
{["Address"], [{"to_str", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]}
|
||||
{["Address"], [{"to_str", 1}, {"to_contract", 1}, {"is_oracle", 1}, {"is_contract", 1}, {"is_payable", 1}]}
|
||||
],
|
||||
maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}}
|
||||
|| {NS, Funs} <- Scopes,
|
||||
@@ -904,6 +904,7 @@ op_builtins() ->
|
||||
string_length, string_concat, string_sha3, string_sha256, string_blake2b,
|
||||
bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union,
|
||||
bits_difference, int_to_str, address_to_str, crypto_verify_sig,
|
||||
address_to_contract,
|
||||
crypto_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b,
|
||||
crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1
|
||||
].
|
||||
|
||||
@@ -494,6 +494,7 @@ is_builtin_fun({qid, _, ["Address", "to_str"]}, _Icode) ->
|
||||
is_builtin_fun({qid, _, ["Address", "is_oracle"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Address", "is_contract"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Address", "is_payable"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Address", "to_contract"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Bytes", "to_int"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Bytes", "to_str"]}, _Icode) -> true;
|
||||
is_builtin_fun({qid, _, ["Bytes", "concat"]}, _Icode) -> true;
|
||||
@@ -713,6 +714,8 @@ builtin_code(_, {qid, _, ["Address", "is_contract"]}, [Addr], _, _, Icode) ->
|
||||
builtin_code(_, {qid, _, ["Address", "is_payable"]}, [Addr], _, _, Icode) ->
|
||||
prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0},
|
||||
[ast_body(Addr, Icode)], [word], word);
|
||||
builtin_code(_, {qid, _, ["Address", "to_contract"]}, [Addr], _, _, Icode) ->
|
||||
ast_body(Addr, Icode);
|
||||
|
||||
builtin_code(_, {qid, _, ["Bytes", "to_int"]}, [Bytes], _, _, Icode) ->
|
||||
{typed, _, _, {bytes_t, _, N}} = Bytes,
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
, create_calldata/3 %% deprecated
|
||||
, create_calldata/4
|
||||
, version/0
|
||||
, numeric_version/0
|
||||
, sophia_type_to_typerep/1
|
||||
, to_sophia_value/4 %% deprecated, need a backend
|
||||
, to_sophia_value/5
|
||||
@@ -22,6 +23,7 @@
|
||||
, decode_calldata/4
|
||||
, parse/2
|
||||
, add_include_path/2
|
||||
, validate_byte_code/3
|
||||
]).
|
||||
|
||||
-include_lib("aebytecode/include/aeb_opcodes.hrl").
|
||||
@@ -65,6 +67,17 @@ version() ->
|
||||
{ok, list_to_binary(VsnString)}
|
||||
end.
|
||||
|
||||
-spec numeric_version() -> {ok, [non_neg_integer()]} | {error, term()}.
|
||||
numeric_version() ->
|
||||
case version() of
|
||||
{ok, Bin} ->
|
||||
[NoSuf | _] = binary:split(Bin, <<"-">>),
|
||||
Numbers = binary:split(NoSuf, <<".">>, [global]),
|
||||
{ok, [binary_to_integer(Num) || Num <- Numbers]};
|
||||
{error, _} = Err ->
|
||||
Err
|
||||
end.
|
||||
|
||||
-spec file(string()) -> {ok, map()} | {error, [aeso_errors:error()]}.
|
||||
file(Filename) ->
|
||||
file(Filename, []).
|
||||
@@ -495,6 +508,14 @@ icode_to_term(T = {map, KT, VT}, M) ->
|
||||
#{};
|
||||
_ -> throw({todo, M})
|
||||
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, _) ->
|
||||
throw({todo, typerep});
|
||||
icode_to_term(T, V) ->
|
||||
@@ -546,6 +567,87 @@ pp(Code, Options, Option, PPFun) ->
|
||||
ok
|
||||
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) ->
|
||||
|
||||
+286
-247
@@ -11,19 +11,23 @@
|
||||
|
||||
-export([compile/2, term_to_fate/1]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-export([optimize_fun/4, to_basic_blocks/1]).
|
||||
-endif.
|
||||
|
||||
%% -- Preamble ---------------------------------------------------------------
|
||||
|
||||
-type scode() :: [sinstr()].
|
||||
-type sinstr() :: {switch, arg(), stype(), [maybe_scode()], maybe_scode()} %% last arg is catch-all
|
||||
| switch_body
|
||||
| tuple(). %% FATE instruction
|
||||
| loop
|
||||
| tuple() | atom(). %% FATE instruction
|
||||
|
||||
-type arg() :: tuple(). %% Not exported: aeb_fate_ops:fate_arg().
|
||||
|
||||
%% Annotated scode
|
||||
-type scode_a() :: [sinstr_a()].
|
||||
-type sinstr_a() :: {switch, arg(), stype(), [maybe_scode_a()], maybe_scode_a()} %% last arg is catch-all
|
||||
| switch_body
|
||||
| {i, ann(), tuple()}. %% FATE instruction
|
||||
|
||||
-type ann() :: #{ live_in := vars(), live_out := vars() }.
|
||||
@@ -41,81 +45,6 @@
|
||||
-define(s, {store, 1}).
|
||||
-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 =:= '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 }).
|
||||
|
||||
%% -- Debugging --------------------------------------------------------------
|
||||
@@ -124,12 +53,19 @@ is_debug(Tag, Options) ->
|
||||
Tags = proplists:get_value(debug, Options, []),
|
||||
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
|
||||
true -> io:format(Fmt, Args);
|
||||
true -> Fun();
|
||||
false -> ok
|
||||
end.
|
||||
|
||||
-dialyzer({nowarn_function, [code_error/1]}).
|
||||
code_error(Err) ->
|
||||
aeso_errors:throw(aeso_code_errors:format(Err)).
|
||||
|
||||
%% -- Main -------------------------------------------------------------------
|
||||
|
||||
%% @doc Main entry point.
|
||||
@@ -139,7 +75,7 @@ compile(FCode, Options) ->
|
||||
SFuns = functions_to_scode(ContractName, Functions, Options),
|
||||
SFuns1 = optimize_scode(SFuns, Options),
|
||||
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.
|
||||
|
||||
make_function_id(X) ->
|
||||
@@ -222,8 +158,6 @@ bind_local(Name, Env) ->
|
||||
|
||||
notail(Env) -> Env#env{ tailpos = false }.
|
||||
|
||||
code_error(Err) -> error(Err).
|
||||
|
||||
lookup_var(#env{vars = Vars}, X) ->
|
||||
case lists:keyfind(X, 1, Vars) of
|
||||
{_, Var} -> Var;
|
||||
@@ -260,6 +194,18 @@ term_to_fate({tuple, As}) ->
|
||||
term_to_fate({con, Ar, I, As}) ->
|
||||
FateAs = [ term_to_fate(A) || A <- As ],
|
||||
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, []}) ->
|
||||
aeb_fate_data:make_map(#{});
|
||||
term_to_fate({'let', _, {builtin, map_empty, []}, Set}) ->
|
||||
@@ -611,6 +557,7 @@ op_to_scode(bits_difference) -> aeb_fate_ops:bits_diff(?a, ?a, ?a);
|
||||
op_to_scode(address_to_str) -> aeb_fate_ops:addr_to_str(?a, ?a);
|
||||
op_to_scode(int_to_str) -> aeb_fate_ops:int_to_str(?a, ?a);
|
||||
op_to_scode(contract_to_address) -> aeb_fate_ops:contract_to_address(?a, ?a);
|
||||
op_to_scode(address_to_contract) -> aeb_fate_ops:address_to_contract(?a, ?a);
|
||||
op_to_scode(crypto_verify_sig) -> aeb_fate_ops:verify_sig(?a, ?a, ?a, ?a);
|
||||
op_to_scode(crypto_verify_sig_secp256k1) -> aeb_fate_ops:verify_sig_secp256k1(?a, ?a, ?a, ?a);
|
||||
op_to_scode(crypto_ecverify_secp256k1) -> aeb_fate_ops:ecverify_secp256k1(?a, ?a, ?a, ?a);
|
||||
@@ -647,23 +594,23 @@ flatten_s(I) -> I.
|
||||
|
||||
optimize_fun(_Funs, Name, {Attrs, Sig, Code}, Options) ->
|
||||
Code0 = flatten(Code),
|
||||
debug(opt, Options, "Optimizing ~s\n", [Name]),
|
||||
?debug(opt, Options, "Optimizing ~s\n", [Name]),
|
||||
Code1 = simpl_loop(0, Code0, Options),
|
||||
Code2 = desugar(Code1),
|
||||
{Attrs, Sig, Code2}.
|
||||
|
||||
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;
|
||||
simpl_loop(N, Code, Options) ->
|
||||
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),
|
||||
[ 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),
|
||||
case Code == Code2 of
|
||||
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]),
|
||||
Code2;
|
||||
false -> simpl_loop(N + 1, Code2, Options)
|
||||
@@ -683,11 +630,9 @@ pp_ann(Ind, [{switch, Arg, Type, Alts, Def} | Code]) ->
|
||||
|| {Tag, Alt} <- lists:zip(Tags, Alts), Alt /= missing],
|
||||
[[Ind1, "_ =>\n", pp_ann(Ind2, Def)] || Def /= missing],
|
||||
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]) ->
|
||||
Fmt = fun([]) -> "()";
|
||||
(Xs) -> string:join([lists:concat(["var", N]) || {var, N} <- Xs], " ")
|
||||
(Xs) -> string:join([lists:flatten(pp_arg(X)) || X <- Xs], " ")
|
||||
end,
|
||||
Op = [Ind, pp_op(desugar_args(I))],
|
||||
Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []],
|
||||
@@ -695,70 +640,67 @@ pp_ann(Ind, [{i, #{ live_in := In, live_out := Out }, I} | Code]) ->
|
||||
pp_ann(Ind, Code)];
|
||||
pp_ann(_, []) -> [].
|
||||
|
||||
pp_op(switch_body) -> "SWITCH-BODY";
|
||||
pp_op(loop) -> "LOOP";
|
||||
pp_op(I) ->
|
||||
aeb_fate_pp:format_op(I, #{}).
|
||||
|
||||
pp_arg(?i(I)) -> io_lib:format("~w", [I]);
|
||||
pp_arg({arg, N}) -> io_lib:format("arg~p", [N]);
|
||||
pp_arg(?s) -> "store1";
|
||||
pp_arg({var, N}) -> io_lib:format("var~p", [N]);
|
||||
pp_arg(?a) -> "a".
|
||||
pp_arg(?i(I)) -> io_lib:format("~w", [I]);
|
||||
pp_arg({arg, N}) -> io_lib:format("arg~p", [N]);
|
||||
pp_arg({store, N}) -> io_lib:format("store~p", [N]);
|
||||
pp_arg({var, N}) -> io_lib:format("var~p", [N]);
|
||||
pp_arg(?a) -> "a".
|
||||
|
||||
%% -- Analysis --
|
||||
|
||||
annotate_code(Code) ->
|
||||
{WCode, _} = ann_writes(Code, ordsets:new(), []),
|
||||
{RCode, _} = ann_reads(WCode, ordsets:new(), []),
|
||||
RCode.
|
||||
annotate_code(5, [], Code).
|
||||
|
||||
%% Reverses the code
|
||||
ann_writes(missing, Writes, []) -> {missing, Writes};
|
||||
ann_writes([switch_body | Code], Writes, Acc) ->
|
||||
ann_writes(Code, Writes, [switch_body | Acc]);
|
||||
ann_writes([{switch, Arg, Type, Alts, Def} | Code], Writes, Acc) ->
|
||||
{Alts1, WritesAlts} = lists:unzip([ ann_writes(Alt, Writes, []) || Alt <- Alts ]),
|
||||
{Def1, WritesDef} = ann_writes(Def, Writes, []),
|
||||
Writes1 = ordsets:union(Writes, ordsets:intersection([WritesDef | WritesAlts])),
|
||||
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}.
|
||||
annotate_code(Fuel, LiveTop, Code) ->
|
||||
{Code1, LiveIn} = ann_live(LiveTop, Code, []),
|
||||
case LiveIn == LiveTop of
|
||||
true -> Code1;
|
||||
false when Fuel =< 0 ->
|
||||
code_error(liveness_analysis_out_of_fuel);
|
||||
false -> annotate_code(Fuel - 1, LiveIn, Code)
|
||||
end.
|
||||
|
||||
%% Takes reversed code and unreverses it.
|
||||
ann_reads(missing, Reads, []) -> {missing, Reads};
|
||||
ann_reads([switch_body | Code], Reads, Acc) ->
|
||||
ann_reads(Code, Reads, [switch_body | Acc]);
|
||||
ann_reads([{switch, Arg, Type, Alts, Def} | Code], Reads, Acc) ->
|
||||
{Alts1, ReadsAlts} = lists:unzip([ ann_reads(Alt, Reads, []) || Alt <- Alts ]),
|
||||
{Def1, ReadsDef} = ann_reads(Def, Reads, []),
|
||||
Reads1 = ordsets:union([[Arg], Reads, ReadsDef | ReadsAlts]),
|
||||
ann_reads(Code, Reads1, [{switch, Arg, Type, Alts1, Def1} | Acc]);
|
||||
ann_reads([{i, Ann, I} | Code], Reads, Acc) ->
|
||||
#{ writes_in := WritesIn, writes_out := WritesOut } = Ann,
|
||||
#{ read := Rs, write := W, pure := Pure } = attributes(I),
|
||||
ann_live(_LiveTop, missing, _LiveOut) -> {missing, []};
|
||||
ann_live(_LiveTop, [], LiveOut) -> {[], LiveOut};
|
||||
ann_live(LiveTop, [I | Is], LiveOut) ->
|
||||
{Is1, LiveMid} = ann_live(LiveTop, Is, LiveOut),
|
||||
{I1, LiveIn} = ann_live1(LiveTop, I, LiveMid),
|
||||
{[I1 | Is1], LiveIn}.
|
||||
|
||||
ann_live1(_LiveTop, switch_body, LiveOut) ->
|
||||
Ann = #{ live_in => LiveOut, live_out => LiveOut },
|
||||
{{i, Ann, switch_body}, LiveOut};
|
||||
ann_live1(LiveTop, loop, _LiveOut) ->
|
||||
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)
|
||||
Reads1 = Reads -- [W],
|
||||
Reads2 =
|
||||
case {W, Pure andalso not ordsets:is_element(W, Reads)} of
|
||||
%% 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}.
|
||||
LiveIn = ordsets:union(LiveOut -- [W], Reads),
|
||||
Ann = #{ live_in => LiveIn, live_out => LiveOut },
|
||||
{{i, Ann, I}, LiveIn}.
|
||||
|
||||
%% Instruction attributes: reads, writes and purity (pure means no side-effects
|
||||
%% aside from the reads and writes).
|
||||
is_reg(?a) -> false;
|
||||
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) ->
|
||||
Set = fun(L) when is_list(L) -> ordsets:from_list(L);
|
||||
(X) -> ordsets:from_list([X]) end,
|
||||
@@ -767,12 +709,13 @@ attributes(I) ->
|
||||
Impure = fun(W, R) -> Attr(W, R, false) end,
|
||||
case I of
|
||||
loop -> Impure(pc, []);
|
||||
switch_body -> Pure(none, []);
|
||||
'RETURN' -> Impure(pc, []);
|
||||
{'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_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, []);
|
||||
{'JUMP', _} -> Impure(pc, []);
|
||||
{'JUMPIF', A, _} -> Impure(pc, A);
|
||||
@@ -851,31 +794,32 @@ attributes(I) ->
|
||||
{'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]);
|
||||
{'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]);
|
||||
{'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]);
|
||||
{'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]);
|
||||
{'AUTH_TX_HASH', A} -> Pure(A, []);
|
||||
{'BYTES_TO_INT', A, B} -> Pure(A, [B]);
|
||||
{'BYTES_TO_STR', A, B} -> Pure(A, [B]);
|
||||
{'BYTES_CONCAT', 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_QUERY', A, B, C, D, E} -> Impure(A, [B, C, D, E]);
|
||||
{'IS_ORACLE', A, B} -> Impure(A, [B]);
|
||||
{'IS_CONTRACT', A, B} -> Impure(A, [B]);
|
||||
{'IS_PAYABLE', A, B} -> Impure(A, [B]);
|
||||
{'ORACLE_CHECK', A, B, C, D} -> Pure(A, [B, C, D]);
|
||||
{'ORACLE_CHECK_QUERY', A, B, C, D, E} -> Pure(A, [B, C, D, E]);
|
||||
{'IS_ORACLE', A, B} -> Pure(A, [B]);
|
||||
{'IS_CONTRACT', A, B} -> Pure(A, [B]);
|
||||
{'IS_PAYABLE', A, B} -> Pure(A, [B]);
|
||||
{'CREATOR', A} -> Pure(A, []);
|
||||
{'ADDRESS', A} -> Pure(A, []);
|
||||
{'BALANCE', A} -> Impure(A, []);
|
||||
{'BALANCE_OTHER', A, B} -> Impure(A, [B]);
|
||||
{'BALANCE', A} -> Pure(A, []);
|
||||
{'BALANCE_OTHER', A, B} -> Pure(A, [B]);
|
||||
{'ORIGIN', A} -> Pure(A, []);
|
||||
{'CALLER', A} -> Pure(A, []);
|
||||
{'GASPRICE', A} -> Pure(A, []);
|
||||
{'BLOCKHASH', A, B} -> Impure(A, [B]);
|
||||
{'BLOCKHASH', A, B} -> Pure(A, [B]);
|
||||
{'BENEFICIARY', A} -> Pure(A, []);
|
||||
{'TIMESTAMP', A} -> Pure(A, []);
|
||||
{'GENERATION', A} -> Pure(A, []);
|
||||
{'MICROBLOCK', A} -> Pure(A, []);
|
||||
{'DIFFICULTY', A} -> Pure(A, []);
|
||||
{'GASLIMIT', A} -> Pure(A, []);
|
||||
{'GAS', A} -> Impure(?a, A);
|
||||
{'GAS', A} -> Pure(A, []);
|
||||
{'LOG0', A} -> Impure(none, [A]);
|
||||
{'LOG1', A, B} -> Impure(none, [A, B]);
|
||||
{'LOG2', A, B, C} -> Impure(none, [A, B, C]);
|
||||
@@ -887,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_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_GET_ANSWER', A, B, C, D, E} -> Impure(A, [B, C, D, E]);
|
||||
{'ORACLE_GET_QUESTION', A, B, C, D, E}-> Impure(A, [B, C, D, E]);
|
||||
{'ORACLE_QUERY_FEE', A, B} -> Impure(A, [B]);
|
||||
{'AENS_RESOLVE', A, B, C, D} -> Impure(A, [B, C, D]);
|
||||
{'ORACLE_GET_ANSWER', A, B, C, D, E} -> Pure(A, [B, C, D, E]);
|
||||
{'ORACLE_GET_QUESTION', A, B, C, D, E}-> Pure(A, [B, C, D, E]);
|
||||
{'ORACLE_QUERY_FEE', A, B} -> Pure(A, [B]);
|
||||
{'AENS_RESOLVE', A, B, C, D} -> Pure(A, [B, C, D]);
|
||||
{'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_UPDATE' -> Impure(none, []);%% TODO
|
||||
@@ -905,15 +849,17 @@ var_writes({i, _, I}) -> var_writes(I);
|
||||
var_writes(I) ->
|
||||
#{ write := W } = attributes(I),
|
||||
case W of
|
||||
{var, _} -> [W];
|
||||
_ -> []
|
||||
{var, _} -> [W];
|
||||
{arg, _} -> [W];
|
||||
{store, _} -> [W];
|
||||
{stack, _} -> [];
|
||||
none -> [];
|
||||
pc -> []
|
||||
end.
|
||||
|
||||
-spec independent(sinstr_a(), sinstr_a()) -> boolean().
|
||||
%% independent({switch, _, _, _, _}, _) -> false; %% Commented due to Dialyzer whinging
|
||||
independent(_, {switch, _, _, _, _}) -> false;
|
||||
%% independent(switch_body, _) -> true;
|
||||
independent(_, switch_body) -> true;
|
||||
independent({i, _, I}, {i, _, J}) ->
|
||||
#{ write := WI, read := RI, pure := PureI } = attributes(I),
|
||||
#{ write := WJ, read := RJ, pure := PureJ } = attributes(J),
|
||||
@@ -935,8 +881,6 @@ merge_ann(#{ live_in := LiveIn }, #{ live_out := LiveOut }) ->
|
||||
#{ live_in => LiveIn, live_out => LiveOut }.
|
||||
|
||||
%% 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}) ->
|
||||
%% Since I and J are independent the J can't read or write anything in
|
||||
%% that I writes.
|
||||
@@ -948,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 => 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, {i, Ann, _}) -> live_in(R, Ann);
|
||||
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} | _]) ->
|
||||
R == A orelse lists:any(fun(Code) -> live_in(R, Code) end, [Def | Alts]);
|
||||
live_in(_, missing) -> 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).
|
||||
|
||||
%% -- Optimizations --
|
||||
@@ -980,7 +923,7 @@ simpl_top(I, Code, Options) ->
|
||||
simpl_top(?SIMPL_FUEL, 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) ->
|
||||
apply_rules(Fuel, rules(), I, Code, Options).
|
||||
|
||||
@@ -992,7 +935,7 @@ apply_rules(Fuel, Rules, I, Code, Options) ->
|
||||
case is_debug(opt_rules, Options) of
|
||||
true ->
|
||||
{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
|
||||
end,
|
||||
lists:foldr(Cons, Rest, New)
|
||||
@@ -1022,6 +965,7 @@ rules() ->
|
||||
?RULE(r_swap_write),
|
||||
?RULE(r_constant_propagation),
|
||||
?RULE(r_prune_impossible_branches),
|
||||
?RULE(r_single_successful_branch),
|
||||
?RULE(r_inline_store),
|
||||
?RULE(r_float_switch_body)
|
||||
].
|
||||
@@ -1044,8 +988,9 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
true -> false end;
|
||||
r_push_consume(_, _) -> false.
|
||||
|
||||
inline_push(Ann, Arg, Stack, [switch_body | Code], Acc) ->
|
||||
inline_push(Ann, Arg, Stack, Code, [switch_body | Acc]);
|
||||
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], 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) ->
|
||||
case op_view(I) of
|
||||
{Op, R, As} ->
|
||||
@@ -1119,8 +1064,9 @@ r_swap_write(I = {i, _, _}, [J | Code]) ->
|
||||
end;
|
||||
r_swap_write(_, _) -> false.
|
||||
|
||||
r_swap_write(Pre, I, [switch_body | Code]) ->
|
||||
r_swap_write([switch_body | Pre], I, Code);
|
||||
r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1 | Pre], I1, Code);
|
||||
r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
case apply_rules_once(merge_rules(), I, Code0) of
|
||||
{_Rule, New, Rest} ->
|
||||
@@ -1136,18 +1082,20 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
r_swap_write(_, _, _) -> false.
|
||||
|
||||
%% 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)}},
|
||||
case R of
|
||||
?a -> {[Store], Code};
|
||||
_ -> {[Cons, Store], Code}
|
||||
end;
|
||||
r_constant_propagation(Cons = {i, _, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
Cons1 = case R of
|
||||
?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
|
||||
_ -> Cons
|
||||
end,
|
||||
{[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)}},
|
||||
case R of
|
||||
?a -> {[Store], Code};
|
||||
_ -> {[Cons, Store], Code}
|
||||
end;
|
||||
Nil1 = case R of
|
||||
?a -> {i, Ann1, {'NIL', ?void}};
|
||||
_ -> Nil
|
||||
end,
|
||||
{[Nil1, Store], Code};
|
||||
r_constant_propagation({i, Ann, I}, Code) ->
|
||||
case op_view(I) of
|
||||
false -> false;
|
||||
@@ -1164,20 +1112,21 @@ r_constant_propagation({i, Ann, I}, Code) ->
|
||||
end;
|
||||
r_constant_propagation(_, _) -> false.
|
||||
|
||||
eval_op('ADD', [X, Y]) -> X + Y;
|
||||
eval_op('SUB', [X, Y]) -> X - Y;
|
||||
eval_op('MUL', [X, Y]) -> X * Y;
|
||||
eval_op('DIV', [X, Y]) when Y /= 0 -> X div Y;
|
||||
eval_op('MOD', [X, Y]) when Y /= 0 -> X rem Y;
|
||||
eval_op('POW', [_, _]) -> no_eval;
|
||||
eval_op('LT', [X, Y]) -> X < Y;
|
||||
eval_op('GT', [X, Y]) -> X > Y;
|
||||
eval_op('EQ', [X, Y]) -> X =:= Y;
|
||||
eval_op('ELT', [X, Y]) -> X =< Y;
|
||||
eval_op('EGT', [X, Y]) -> X >= Y;
|
||||
eval_op('NEQ', [X, Y]) -> X =/= Y;
|
||||
eval_op('NOT', [X]) -> not X;
|
||||
eval_op(_, _) -> no_eval. %% TODO: bits?
|
||||
eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y;
|
||||
eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
|
||||
eval_op('MUL', [X, Y]) when is_integer(X), is_integer(Y) -> X * Y;
|
||||
eval_op('DIV', [X, Y]) when is_integer(X), is_integer(Y), Y /= 0 -> X div 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('LT', [X, Y]) -> X < Y;
|
||||
eval_op('GT', [X, Y]) -> X > Y;
|
||||
eval_op('EQ', [X, Y]) -> X =:= Y;
|
||||
eval_op('ELT', [X, Y]) -> X =< Y;
|
||||
eval_op('EGT', [X, Y]) -> X >= Y;
|
||||
eval_op('NEQ', [X, Y]) -> X =/= Y;
|
||||
eval_op('NOT', [true]) -> false;
|
||||
eval_op('NOT', [false]) -> true;
|
||||
eval_op(_, _) -> no_eval. %% TODO: bits?
|
||||
|
||||
%% Prune impossible branches from switches
|
||||
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||
@@ -1185,7 +1134,7 @@ r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||
false -> false;
|
||||
Alt -> {Alt, Code}
|
||||
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,
|
||||
if V -> True; true -> missing end],
|
||||
case Alts == Alts1 of
|
||||
@@ -1197,7 +1146,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
|
||||
end
|
||||
end;
|
||||
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
|
||||
{_, missing} ->
|
||||
Alts1 = [missing || _ <- Alts],
|
||||
@@ -1214,7 +1163,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
|
||||
end;
|
||||
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,
|
||||
case Alt of
|
||||
missing -> false;
|
||||
@@ -1223,10 +1172,62 @@ pick_branch(boolean, V, [False, True]) ->
|
||||
pick_branch(_Type, _V, _Alts) ->
|
||||
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
|
||||
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},
|
||||
case R of
|
||||
A -> false;
|
||||
?a -> {[Switch], Code};
|
||||
{var, _} ->
|
||||
case lists:any(fun(Alt) -> live_in(R, Alt) end, [Def | Alts]) of
|
||||
@@ -1239,8 +1240,9 @@ r_inline_switch_target(Store = {i, _, {'STORE', R, A}}, [{switch, R, Type, Alts,
|
||||
r_inline_switch_target(_, _) -> false.
|
||||
|
||||
%% Float switch-body to closest switch
|
||||
r_float_switch_body(I = {i, _, _}, [switch_body | Code]) ->
|
||||
{[], [switch_body, I | Code]};
|
||||
r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
{[], [J1, I1 | Code]};
|
||||
r_float_switch_body(_, _) -> false.
|
||||
|
||||
%% Inline stores
|
||||
@@ -1249,43 +1251,47 @@ r_inline_store({i, _, {'STORE', R, R}}, Code) ->
|
||||
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
%% Not when A is var unless updating the annotations properly.
|
||||
Inline = case A of
|
||||
{arg, _} -> true;
|
||||
?i(_) -> true;
|
||||
_ -> false
|
||||
{arg, _} -> true;
|
||||
?i(_) -> true;
|
||||
{store, _} -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if Inline -> r_inline_store([I], R, A, Code);
|
||||
if Inline -> r_inline_store([I], false, R, A, Code);
|
||||
true -> false end;
|
||||
r_inline_store(_, _) -> false.
|
||||
|
||||
r_inline_store(Acc, R, A, [switch_body | Code]) ->
|
||||
r_inline_store([switch_body | Acc], R, A, Code);
|
||||
r_inline_store(Acc, R, A, [{i, Ann, I} | Code]) ->
|
||||
#{ write := W, pure := Pure } = attributes(I),
|
||||
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
|
||||
r_inline_store([I | Acc], Progress, R, A, Code);
|
||||
r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
#{ write := W } = attributes(I),
|
||||
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
|
||||
true -> false;
|
||||
false ->
|
||||
case op_view(I) of
|
||||
{Op, S, As} ->
|
||||
case lists:member(R, As) of
|
||||
true ->
|
||||
Acc1 = [{i, Ann, from_op_view(Op, S, lists:map(Inl, As))} | Acc],
|
||||
case r_inline_store(Acc1, R, A, Code) of
|
||||
false -> {lists:reverse(Acc1), Code};
|
||||
{_, _} = Res -> Res
|
||||
end;
|
||||
false ->
|
||||
r_inline_store([{i, Ann, I} | Acc], R, A, Code)
|
||||
end;
|
||||
_ -> r_inline_store([{i, Ann, I} | Acc], R, A, Code)
|
||||
case live_in(R, Ann) of
|
||||
false -> false; %% No more reads of R
|
||||
true ->
|
||||
{I1, Progress1} =
|
||||
case op_view(I) of
|
||||
{Op, S, As} ->
|
||||
case lists:member(R, As) of
|
||||
true -> {from_op_view(Op, S, lists:map(Inl, As)), true};
|
||||
false -> {I, Progress}
|
||||
end;
|
||||
_ -> {I, Progress}
|
||||
end,
|
||||
Acc1 = [{i, Ann, I1} | Acc],
|
||||
%% Stop if write to R or A
|
||||
case lists:member(W, [R, A]) of
|
||||
true when Progress1 -> {lists:reverse(Acc1), Code};
|
||||
true -> false;
|
||||
false -> r_inline_store(Acc1, Progress1, R, A, Code)
|
||||
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
|
||||
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
case op_view(I) of
|
||||
{Op, R, As} ->
|
||||
{Op, R = {var, _}, As} ->
|
||||
Copy = case J of
|
||||
{'STORE', S, R} -> {write_to, S};
|
||||
_ -> false
|
||||
@@ -1302,8 +1308,9 @@ r_one_shot_var(_, _) -> false.
|
||||
%% Remove writes to dead variables
|
||||
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
|
||||
r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
#{ pure := Pure } = attributes(I),
|
||||
case op_view(I) of
|
||||
{_Op, R = {var, _}, As} ->
|
||||
{_Op, R, As} when R /= ?a, Pure ->
|
||||
case live_out(R, Ann) of
|
||||
false ->
|
||||
%% Subtle: we still have to pop the stack if any of the arguments
|
||||
@@ -1315,21 +1322,24 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
end;
|
||||
r_write_to_dead_var(_, _) -> false.
|
||||
|
||||
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
|
||||
op_view(T) when is_tuple(T) ->
|
||||
case tuple_to_list(T) of
|
||||
[Op, R | As] when ?IsOp(Op) ->
|
||||
{Op, R, As};
|
||||
_ -> false
|
||||
[Op, R | As] = tuple_to_list(T),
|
||||
CheckReads = fun(Rs, X) -> case [] == Rs -- [dst, src] of true -> X; false -> false end end,
|
||||
case attributes(list_to_tuple([Op, dst | [src || _ <- As]])) of
|
||||
#{ write := dst, read := Rs } -> CheckReads(Rs, {Op, R, As});
|
||||
#{ write := none, read := Rs } -> CheckReads(Rs, {Op, none, [R | As]});
|
||||
_ -> false
|
||||
end;
|
||||
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]).
|
||||
|
||||
%% Desugar and specialize and remove annotations
|
||||
-spec unannotate(scode_a()) -> scode();
|
||||
(sinstr_a()) -> sinstr();
|
||||
(missing) -> missing.
|
||||
unannotate(switch_body) -> [switch_body];
|
||||
unannotate({switch, Arg, Type, Alts, Def}) ->
|
||||
[{switch, Arg, Type, [unannotate(A) || A <- Alts], unannotate(Def)}];
|
||||
unannotate(missing) -> missing;
|
||||
@@ -1345,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_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}) ->
|
||||
[{switch, desugar_arg(Arg), Type, [desugar(A) || A <- Alts], desugar(Def)}];
|
||||
desugar(missing) -> missing;
|
||||
@@ -1357,7 +1368,7 @@ desugar_args(I) when is_tuple(I) ->
|
||||
list_to_tuple([Op | lists:map(fun desugar_arg/1, Args)]);
|
||||
desugar_args(I) -> I.
|
||||
|
||||
desugar_arg(?s) -> {var, -1};
|
||||
desugar_arg({store, N}) -> {var, -N};
|
||||
desugar_arg(A) -> A.
|
||||
|
||||
%% -- Phase III --------------------------------------------------------------
|
||||
@@ -1423,11 +1434,13 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
||||
{DefRef, DefBlk} =
|
||||
case Default of
|
||||
missing when Catchall == none ->
|
||||
FreshBlk([aeb_fate_ops:exit(?i(<<"Incomplete patterns">>))], none);
|
||||
FreshBlk([aeb_fate_ops:abort(?i(<<"Incomplete patterns">>))], none);
|
||||
missing -> {Catchall, []};
|
||||
_ -> FreshBlk(Default ++ [{jump, RestRef}], Catchall)
|
||||
%% ^ fall-through to the outer catchall
|
||||
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} =
|
||||
case Type of
|
||||
boolean ->
|
||||
@@ -1443,7 +1456,7 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
||||
_ -> FalseCode ++ [{jump, RestRef}]
|
||||
end,
|
||||
case lists:usort(Alts) == [missing] of
|
||||
true -> {Blk#blk{code = [{jump, DefRef}]}, [], []};
|
||||
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
||||
false ->
|
||||
case Arg of
|
||||
?i(false) -> {Blk#blk{code = ElseCode}, [], ThenBlk};
|
||||
@@ -1453,18 +1466,28 @@ block(Blk = #blk{code = [{switch, Arg, Type, Alts, Default} | Code],
|
||||
end;
|
||||
tuple ->
|
||||
[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, [_]} ->
|
||||
%% [SINGLE_CON_SWITCH] Single constructor switches don't need a
|
||||
%% switch instruction.
|
||||
[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} ->
|
||||
MkBlk = fun(missing) -> {DefRef, []};
|
||||
(ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef)
|
||||
end,
|
||||
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
|
||||
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)}
|
||||
case lists:usort(Alts) == [missing] of
|
||||
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
|
||||
false ->
|
||||
MkBlk = fun(missing) -> {DefRef, []};
|
||||
(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,
|
||||
Blk2 = Blk1#blk{catchall = DefRef}, %% Update catchall ref
|
||||
block(Blk2, Code1 ++ Acc, DefBlk ++ RestBlk ++ AltBlks ++ Blocks, BlockAcc);
|
||||
@@ -1480,9 +1503,10 @@ optimize_blocks(Blocks) ->
|
||||
RBlockMap = maps:from_list(RBlocks),
|
||||
RBlocks1 = reorder_blocks(RBlocks, []),
|
||||
RBlocks2 = [ {Ref, inline_block(RBlockMap, Ref, Code)} || {Ref, Code} <- RBlocks1 ],
|
||||
RBlocks3 = remove_dead_blocks(RBlocks2),
|
||||
RBlocks4 = [ {Ref, tweak_returns(Code)} || {Ref, Code} <- RBlocks3 ],
|
||||
Rev(RBlocks4).
|
||||
RBlocks3 = shortcut_jump_chains(RBlocks2),
|
||||
RBlocks4 = remove_dead_blocks(RBlocks3),
|
||||
RBlocks5 = [ {Ref, tweak_returns(Code)} || {Ref, Code} <- RBlocks4 ],
|
||||
Rev(RBlocks5).
|
||||
|
||||
%% Choose the next block based on the final jump.
|
||||
reorder_blocks([], Acc) ->
|
||||
@@ -1518,6 +1542,21 @@ inline_block(BlockMap, Ref, [{jump, L} | Code] = Code0) when L /= Ref ->
|
||||
end;
|
||||
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_dead_blocks(Blocks = [{Top, _} | _]) ->
|
||||
BlockMap = maps:from_list(Blocks),
|
||||
|
||||
+34
-5
@@ -50,7 +50,8 @@ parse_and_scan(P, S, Opts) ->
|
||||
set_current_file(proplists:get_value(src_file, Opts, no_file)),
|
||||
case aeso_scan:scan(S) of
|
||||
{ok, Tokens} -> aeso_parse_lib:parse(P, Tokens);
|
||||
Error -> Error
|
||||
{error, {{Input, Pos}, _}} ->
|
||||
{error, {Pos, scan_error, Input}}
|
||||
end.
|
||||
|
||||
-dialyzer({nowarn_function, parse_error/1}).
|
||||
@@ -60,8 +61,8 @@ parse_error(Err) ->
|
||||
mk_p_err(Pos, 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_p_err(Pos, "Scan error\n");
|
||||
mk_error({Pos, scan_error, Input}) ->
|
||||
mk_p_err(Pos, io_lib:format("Lexical error on input: ~s\n", [Input]));
|
||||
mk_error({Pos, parse_error, Err}) ->
|
||||
Msg = io_lib:format("~s\n", [Err]),
|
||||
mk_p_err(Pos, Msg);
|
||||
@@ -87,6 +88,7 @@ decl() ->
|
||||
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5}))
|
||||
, ?RULE(keyword(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
|
||||
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
|
||||
, pragma()
|
||||
|
||||
%% Type declarations TODO: format annotation for "type bla" vs "type bla()"
|
||||
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
|
||||
@@ -104,6 +106,16 @@ decl() ->
|
||||
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2))
|
||||
])).
|
||||
|
||||
pragma() ->
|
||||
Op = choice([token(T) || T <- ['<', '=<', '==', '>=', '>']]),
|
||||
?RULE(tok('@'), id("compiler"), Op, version(), {pragma, get_ann(_1), {compiler, element(1, _3), _4}}).
|
||||
|
||||
version() ->
|
||||
?RULE(token(int), many({tok('.'), token(int)}), mk_version(_1, _2)).
|
||||
|
||||
mk_version({int, _, Maj}, Rest) ->
|
||||
[Maj | [N || {_, {int, _, N}} <- Rest]].
|
||||
|
||||
fun_or_entry() ->
|
||||
choice([?RULE(keyword(function), {function, _1}),
|
||||
?RULE(keyword(entrypoint), {entrypoint, _1})]).
|
||||
@@ -406,7 +418,7 @@ token(Tag) ->
|
||||
id(Id) ->
|
||||
?LET_P({id, A, X} = Y, id(),
|
||||
if X == Id -> Y;
|
||||
true -> fail({A, "expected 'bytes'"})
|
||||
true -> fail({A, "expected '" ++ Id ++ "'"})
|
||||
end).
|
||||
|
||||
id_or_addr() ->
|
||||
@@ -607,11 +619,28 @@ read_file(File, Opts) ->
|
||||
case maps:get(binary_to_list(File), Files, not_found) of
|
||||
not_found -> {error, not_found};
|
||||
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.
|
||||
|
||||
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) ->
|
||||
case {read_file(File, Opts), read_file(File, stdlib_options())} of
|
||||
|
||||
@@ -149,6 +149,7 @@ decl({contract, _, C, Ds}) ->
|
||||
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
|
||||
decl({namespace, _, C, Ds}) ->
|
||||
block(follow(text("namespace"), hsep(name(C), text("="))), decls(Ds));
|
||||
decl({pragma, _, Pragma}) -> pragma(Pragma);
|
||||
decl({type_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
|
||||
decl({type_def, _, T, Vars, Def}) ->
|
||||
Kind = element(1, Def),
|
||||
@@ -170,6 +171,10 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
|
||||
hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]);
|
||||
decl(D = {letval, _, _, _, _}) -> letdecl("let", D).
|
||||
|
||||
-spec pragma(aeso_syntax:pragma()) -> doc().
|
||||
pragma({compiler, Op, Ver}) ->
|
||||
text("@compiler " ++ atom_to_list(Op) ++ " " ++ string:join([integer_to_list(N) || N <- Ver], ".")).
|
||||
|
||||
-spec expr(aeso_syntax:expr(), options()) -> doc().
|
||||
expr(E, Options) ->
|
||||
with_options(Options, fun() -> expr(E) end).
|
||||
|
||||
+6
-1
@@ -13,7 +13,7 @@
|
||||
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
|
||||
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
|
||||
-export_type([bin_op/0, un_op/0]).
|
||||
-export_type([decl/0, letbind/0, typedef/0]).
|
||||
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
|
||||
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
|
||||
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
|
||||
-export_type([ast/0]).
|
||||
@@ -36,11 +36,16 @@
|
||||
|
||||
-type decl() :: {contract, ann(), con(), [decl()]}
|
||||
| {namespace, ann(), con(), [decl()]}
|
||||
| {pragma, ann(), pragma()}
|
||||
| {type_decl, ann(), id(), [tvar()]}
|
||||
| {type_def, ann(), id(), [tvar()], typedef()}
|
||||
| {fun_decl, ann(), id(), type()}
|
||||
| letbind().
|
||||
|
||||
-type compiler_version() :: [non_neg_integer()].
|
||||
|
||||
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
|
||||
|
||||
-type letbind()
|
||||
:: {letval, ann(), id(), type(), expr()}
|
||||
| {letfun, ann(), id(), [arg()], type(), expr()}.
|
||||
|
||||
+27
-5
@@ -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_query"}, _}, N) -> address_literal(oracle_query_id, 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, _, "bits"}, N) -> error({todo, bits, N});
|
||||
from_aevm(word, {id, _, "bool"}, N) -> {bool, [], N /= 0};
|
||||
from_aevm(word, {id, _, "int"}, N0) ->
|
||||
<<N:256/signed>> = <<N0:256>>,
|
||||
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 ->
|
||||
<<Bytes:Len/unit:8, _/binary>> = <<Val:32/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),
|
||||
ConType = lists:nth(Tag + 1, Cons),
|
||||
from_aevm(VmTypes, ConType, Args);
|
||||
from_aevm([], {constr_t, _, Con, []}, []) -> Con;
|
||||
from_aevm(VmTypes, {constr_t, _, Con, Types}, Args)
|
||||
when length(VmTypes) == length(Types),
|
||||
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({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({id, _, "bits"}, ?FATE_BITS(Bin)) -> error({todo, bits, Bin});
|
||||
from_fate({id, _, "int"}, N) when is_integer(N) -> {int, [], N};
|
||||
from_fate({id, _, "bits"}, ?FATE_BITS(N)) -> make_bits(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, _, "string"}, S) when is_binary(S) -> {string, [], S};
|
||||
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);
|
||||
_ -> throw(cannot_translate_to_sophia)
|
||||
end;
|
||||
from_fate({constr_t, _, Con, []}, []) -> Con;
|
||||
from_fate({constr_t, _, Con, Types}, Args)
|
||||
when length(Types) == length(Args) ->
|
||||
{app, [], Con, [ from_fate(Type, Arg)
|
||||
|| {Type, Arg} <- lists:zip(Types, Args) ]};
|
||||
from_fate(_Type, _Data) ->
|
||||
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,6 +1,6 @@
|
||||
{application, aesophia,
|
||||
[{description, "Contract Language for aeternity"},
|
||||
{vsn, "4.0.0-rc5"},
|
||||
{vsn, "4.1.0-rc1"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
|
||||
@@ -62,7 +62,7 @@ encode_decode_sophia_test() ->
|
||||
Other -> Other
|
||||
end end,
|
||||
ok = Check("int", "42"),
|
||||
ok = Check("int", "-42"),
|
||||
ok = Check("int", "- 42"),
|
||||
ok = Check("bool", "true"),
|
||||
ok = Check("bool", "false"),
|
||||
ok = Check("string", "\"Hello\""),
|
||||
|
||||
@@ -29,11 +29,10 @@ calldata_test_() ->
|
||||
true -> ast_exprs(ContractString, Fun, Args, [{backend, fate}]);
|
||||
false -> undefined
|
||||
end,
|
||||
case FateExprs == undefined orelse AevmExprs == undefined of
|
||||
true -> ok;
|
||||
false ->
|
||||
?assertEqual(FateExprs, AevmExprs)
|
||||
end
|
||||
ParsedExprs = parse_args(Fun, Args),
|
||||
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
|
||||
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
|
||||
ok
|
||||
end} || {ContractName, Fun, Args} <- compilable_contracts()].
|
||||
|
||||
calldata_aci_test_() ->
|
||||
@@ -53,19 +52,34 @@ calldata_aci_test_() ->
|
||||
true -> ast_exprs(ContractACI, Fun, Args, [{backend, fate}]);
|
||||
false -> undefined
|
||||
end,
|
||||
case FateExprs == undefined orelse AevmExprs == undefined of
|
||||
true -> ok;
|
||||
false ->
|
||||
?assertEqual(FateExprs, AevmExprs)
|
||||
end
|
||||
ParsedExprs = parse_args(Fun, Args),
|
||||
[ ?assertEqual(ParsedExprs, AevmExprs) || AevmExprs /= undefined ],
|
||||
[ ?assertEqual(ParsedExprs, FateExprs) || FateExprs /= undefined ],
|
||||
ok
|
||||
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) ->
|
||||
{ok, Data} = (catch aeso_compiler:create_calldata(ContractString, Fun, Args, Opts)),
|
||||
{ok, _Types, Exprs} = (catch aeso_compiler:decode_calldata(ContractString, Fun, Data, Opts)),
|
||||
?assert(is_list(Exprs)),
|
||||
Exprs.
|
||||
strip_ann(Exprs).
|
||||
|
||||
check_errors(Expect, ErrorString) ->
|
||||
%% This removes the final single \n as well.
|
||||
@@ -85,7 +99,9 @@ compilable_contracts() ->
|
||||
{"maps", "init", []},
|
||||
{"funargs", "menot", ["false"]},
|
||||
{"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", "sjutton", ["#0011012003100011012003100011012003"]},
|
||||
{"funargs", "sextiosju", ["#01020304050607080910111213141516171819202122232425262728293031323334353637383940"
|
||||
|
||||
@@ -12,6 +12,14 @@
|
||||
|
||||
-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
|
||||
%% are made on the output, just that it is a binary which indicates
|
||||
%% that the compilation worked.
|
||||
@@ -176,6 +184,8 @@ not_yet_compilable(aevm) -> [].
|
||||
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
|
||||
|
||||
failing_contracts() ->
|
||||
{ok, V} = aeso_compiler:numeric_version(),
|
||||
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
|
||||
%% Parse errors
|
||||
[ ?PARSE_ERROR(field_parse_error,
|
||||
[<<?Pos(5, 26)
|
||||
@@ -356,19 +366,19 @@ failing_contracts() ->
|
||||
, ?TYPE_ERROR(bad_address_literals,
|
||||
[<<?Pos(32, 5)
|
||||
"The type bytes(32) is not a contract type\n"
|
||||
"when checking that the contract literal at line 32, column 5\n"
|
||||
"when checking that the contract literal\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" bytes(32)">>,
|
||||
<<?Pos(30, 5)
|
||||
"The type oracle(int, bool) is not a contract type\n"
|
||||
"when checking that the contract literal at line 30, column 5\n"
|
||||
"when checking that the contract literal\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" oracle(int, bool)">>,
|
||||
<<?Pos(28, 5)
|
||||
"The type address is not a contract type\n"
|
||||
"when checking that the contract literal at line 28, column 5\n"
|
||||
"when checking that the contract literal\n"
|
||||
" ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
|
||||
"has the type\n"
|
||||
" address">>,
|
||||
@@ -440,7 +450,13 @@ failing_contracts() ->
|
||||
"when checking the type of the expression at line 7, column 5\n"
|
||||
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
|
||||
"against the expected type\n"
|
||||
" bytes(32)">>])
|
||||
" bytes(32)">>,
|
||||
<<?Pos(34, 5),
|
||||
"The type address is not a contract type\n"
|
||||
"when checking that the call to\n"
|
||||
" Address.to_contract\n"
|
||||
"has the type\n"
|
||||
" address">>])
|
||||
, ?TYPE_ERROR(stateful,
|
||||
[<<?Pos(13, 35)
|
||||
"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>,
|
||||
@@ -564,6 +580,21 @@ failing_contracts() ->
|
||||
"and result types\n"
|
||||
" - bytes(20) (at line 18, column 25)\n"
|
||||
" - 'a (at line 19, column 5)">>])
|
||||
, ?TYPE_ERROR(wrong_compiler_version,
|
||||
[<<?Pos(1, 1)
|
||||
"Cannot compile with this version of the compiler,\n"
|
||||
"because it does not satisfy the constraint ", Version/binary, " < 1.0">>,
|
||||
<<?Pos(2, 1)
|
||||
"Cannot compile with this version of the compiler,\n"
|
||||
"because it does not satisfy the constraint ", Version/binary, " == 9.9.9">>])
|
||||
, ?TYPE_ERROR(multiple_contracts,
|
||||
[<<?Pos(2, 3)
|
||||
"Only the main contract can contain defined functions or entrypoints.\n"
|
||||
"Fix: replace the definition of 'foo' by a type signature.">>])
|
||||
, ?TYPE_ERROR(contract_as_namespace,
|
||||
[<<?Pos(5, 28)
|
||||
"Invalid call to contract entrypoint 'Foo.foo'.\n"
|
||||
"It must be called as 'c.foo' for some c : Foo.">>])
|
||||
].
|
||||
|
||||
-define(Path(File), "code_errors/" ??File).
|
||||
@@ -679,3 +710,44 @@ failing_code_gen_contracts() ->
|
||||
"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()]}}]).
|
||||
|
||||
|
||||
@@ -11,4 +11,6 @@ contract AddressLiterals =
|
||||
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
entrypoint contr() : Remote =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
entrypoint contr_addr() : Remote =
|
||||
Address.to_contract(addr())
|
||||
|
||||
|
||||
@@ -30,4 +30,6 @@ contract AddressLiterals =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
entrypoint contr3() : bytes(32) =
|
||||
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
|
||||
entrypoint contr4() : address =
|
||||
Address.to_contract(Contract.address)
|
||||
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
contract Foo =
|
||||
entrypoint foo : () => int
|
||||
|
||||
contract Fail =
|
||||
entrypoint bad() : int = Foo.foo()
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract ContractOne =
|
||||
entrypoint foo() = "foo"
|
||||
|
||||
contract ContractTwo =
|
||||
entrypoint bar() = "bar"
|
||||
@@ -0,0 +1,4 @@
|
||||
contract ValidationTest =
|
||||
payable entrypoint attr_fail() = ()
|
||||
entrypoint type_fail(x : int) = x
|
||||
entrypoint code_fail(x) = x + 1
|
||||
@@ -0,0 +1,4 @@
|
||||
contract ValidationTest =
|
||||
entrypoint attr_fail() = ()
|
||||
entrypoint type_fail(x) = x
|
||||
entrypoint code_fail(x) = x - 1
|
||||
@@ -0,0 +1,4 @@
|
||||
payable contract ValidationTest =
|
||||
payable entrypoint attr_fail() = ()
|
||||
entrypoint type_fail(x : int) = x
|
||||
entrypoint code_fail(x) = x + 1
|
||||
@@ -0,0 +1,7 @@
|
||||
@compiler < 1.0
|
||||
@compiler == 9.9.9
|
||||
@compiler >= 0.1
|
||||
@compiler =< 100.0.1
|
||||
|
||||
contract Fail =
|
||||
entrypoint foo() = ()
|
||||
Reference in New Issue
Block a user