Compare commits

...

78 Commits

Author SHA1 Message Date
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
Hans Svensson b81312a714 Merge pull request #165 from aeternity/GH-164-prepare_release
Prepare release - v4.0.0
2019-10-11 15:52:29 +02:00
Hans Svensson 63c0b714d0 Prepare release - v4.0.0 2019-10-11 15:16:00 +02:00
Ulf Norell d018cc5819 Merge pull request #168 from aeternity/compiler-bug
Fix bug in compiler optimization
2019-10-10 14:35:56 +02:00
Ulf Norell f5b2732b04 Don't get rid of store updates! 2019-10-10 14:19:56 +02:00
Hans Svensson f86f7984f4 Merge pull request #167 from aeternity/radrow-patch-1
Fix not instantiated uvar
2019-10-10 08:31:48 +02:00
Radosław Rowicki 1ae0a42071 Fix not instantiated uvar 2019-10-09 17:42:12 +02:00
Ulf Norell 18ae801333 Merge pull request #162 from aeternity/address-to-contract
Add Address.to_contract
2019-10-01 14:28:32 +02:00
Ulf Norell 32d52f0abc Merge pull request #163 from aeternity/fail-on-multiple-contracts
Fail on defined functions in contract prototypes
2019-10-01 14:28:06 +02:00
Ulf Norell 5e6ff6c9a7 Nice type error if contract function is called as from a namespace 2019-10-01 14:13:56 +02:00
Ulf Norell 2d6d506d63 Fail on function definitions in contracts other than the main contract 2019-10-01 14:13:54 +02:00
Ulf Norell 482d22d46b Merge pull request #161 from aeternity/version-pragmas
add pragma to check compiler version
2019-10-01 14:10:06 +02:00
Ulf Norell a333888fb9 aebytecode commit 2019-09-30 14:47:26 +02:00
Ulf Norell 5fc6e18cd2 Add Address.to_contract
Casts an address to a (any) contract type.
2019-09-30 14:47:05 +02:00
Ulf Norell dd94a6bd67 add pragma to check compiler version 2019-09-27 17:31:10 +02:00
Hans Svensson 7f86b7d301 Merge pull request #160 from aeternity/prepare_rc5
Prepare 4.0.0-rc5
2019-09-27 09:34:49 +02:00
Hans Svensson e018c31ce1 Prepare 4.0.0-rc5 2019-09-27 09:06:52 +02:00
Ulf Norell 9234690d31 Merge pull request #159 from aeternity/more-compiler-fixes
Fix issues with liveness analysis
2019-09-27 08:45:37 +02:00
Ulf Norell 214a5f0a91 Fix issues with liveness analysis 2019-09-24 16:23:50 +02:00
Ulf Norell d4d3a9650a Merge pull request #158 from aeternity/fix-code-generation-bug
Don't confuse variables and store registers in fate asm generation
2019-09-24 14:55:08 +02:00
Ulf Norell b752965443 don't call aeb_fate_ops with {store, _} arg
(to not upset dialyzer)
2019-09-24 10:47:26 +02:00
Ulf Norell 0019d92e45 Don't confuse variables and store registers in fate asm generation 2019-09-23 16:52:16 +02:00
Ulf Norell 29f2168827 Merge pull request #157 from aeternity/big-number-literals
Allow underscore separators in number and bytes literals
2019-09-23 14:11:11 +02:00
Ulf Norell f81dc88526 Allow underscore separators in number and bytes literals
For instance, `1_000_000_000` or `#FFFF_FFFF_FFFF_FFFF`
2019-09-23 14:04:09 +02:00
Ulf Norell a21715a657 Merge pull request #156 from aeternity/fix-compiler-crash-bug
Fix bug with missing fields causing compiler crash
2019-09-23 11:56:30 +02:00
Ulf Norell 048c2ca98d Fix bug with missing fields causing compiler crash
... if under a lambda or switch.
2019-09-23 11:40:15 +02:00
Ulf Norell 662e5e70ef Merge pull request #153 from aeternity/src-loc-for-fun-app
Fix bug with missing source location for function applications
2019-09-14 15:36:02 +02:00
Ulf Norell 8e3483ced4 Merge pull request #154 from aeternity/bug-with-old-tuple-syntax
Fix bug when using old tuple syntax
2019-09-14 15:35:37 +02:00
Ulf Norell 6efc390bb6 Fix bug when using old tuple syntax 2019-09-14 14:44:25 +02:00
Ulf Norell 981027b2e7 Test case for function application source location 2019-09-14 12:12:55 +02:00
Ulf Norell 11d998b739 Set source location for function applications 2019-09-14 12:09:02 +02:00
Hans Svensson b481b3254b Merge pull request #152 from aeternity/prepare_rc4
Prepare v4.0.0-rc4
2019-09-13 08:12:05 +02:00
Hans Svensson 01a2efb7b8 Prepare v4.0.0-rc4 2019-09-13 08:08:47 +02:00
Hans Svensson a730fcc366 Merge pull request #151 from aeternity/fix_numeric_escapes
Fix numeric escapes in strings
2019-09-13 08:04:06 +02:00
Hans Svensson 457f9cf4ea Remove comment + CHANGELOG 2019-09-12 21:31:25 +02:00
Hans Svensson f34b6ed982 Fix numeric escapes 2019-09-12 21:17:01 +02:00
28 changed files with 822 additions and 358 deletions
+29 -16
View File
@@ -9,28 +9,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
### Removed ### Removed
## [4.0.0-rc3] - 2019-09-12 ## [4.1.0-rc1] - 2019-11-25
### Added ### 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
### 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.
- `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.
@@ -47,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,
@@ -154,7 +164,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Simplify calldata creation - instead of passing a compiled contract, simply - Simplify calldata creation - instead of passing a compiled contract, simply
pass a (stubbed) contract string. pass a (stubbed) contract string.
[Unreleased]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc3...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 [4.0.0-rc3]: https://github.com/aeternity/aesophia/compare/v4.0.0-rc1...v4.0.0-rc3
[4.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0-rc1 [4.0.0-rc1]: https://github.com/aeternity/aesophia/compare/v3.2.0...v4.0.0-rc1
[3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0 [3.2.0]: https://github.com/aeternity/aesophia/compare/v3.1.0...v3.2.0
+2 -2
View File
@@ -2,7 +2,7 @@
{erl_opts, [debug_info]}. {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"} , {getopt, "1.0.1"}
, {eblake2, "1.0.0"} , {eblake2, "1.0.0"}
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", , {jsx, {git, "https://github.com/talentdeficit/jsx.git",
@@ -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-rc3"}, {relx, [{release, {aesophia, "4.1.0-rc1"},
[aesophia, aebytecode, getopt]}, [aesophia, aebytecode, getopt]},
{dev_mode, true}, {dev_mode, true},
+1 -1
View File
@@ -1,7 +1,7 @@
{"1.1.0", {"1.1.0",
[{<<"aebytecode">>, [{<<"aebytecode">>,
{git,"https://github.com/aeternity/aebytecode.git", {git,"https://github.com/aeternity/aebytecode.git",
{ref,"a66dc0a97facdeaad7e5403018ad195d989e4793"}}, {ref,"4f4d6d30cd2c46b3830454d650a424d513f69134"}},
0}, 0},
{<<"aeserialization">>, {<<"aeserialization">>,
{git,"https://github.com/aeternity/aeserialization.git", {git,"https://github.com/aeternity/aeserialization.git",
+108 -35
View File
@@ -60,7 +60,8 @@
-record(is_contract_constraint, -record(is_contract_constraint,
{ contract_t :: utype(), { 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{}. -type field_constraint() :: #field_constraint{} | #record_create_constraint{} | #is_contract_constraint{}.
@@ -83,7 +84,7 @@
-type qname() :: [string()]. -type qname() :: [string()].
-type typesig() :: {type_sig, aeso_syntax:ann(), type_constraints(), [aeso_syntax:named_arg_t()], [type()], type()}. -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 fun_info() :: {aeso_syntax:ann(), typesig() | type()}.
-type type_info() :: {aeso_syntax:ann(), typedef()}. -type type_info() :: {aeso_syntax:ann(), typedef()}.
@@ -110,7 +111,7 @@
, in_pattern = false :: boolean() , in_pattern = false :: boolean()
, stateful = false :: boolean() , stateful = false :: boolean()
, current_function = none :: none | aeso_syntax:id() , current_function = none :: none | aeso_syntax:id()
, what = top :: top | namespace | contract , what = top :: top | namespace | contract | main_contract
}). }).
-type env() :: #env{}. -type env() :: #env{}.
@@ -175,12 +176,13 @@ bind_fun(X, Type, Env) ->
end. end.
-spec force_bind_fun(name(), type() | typesig(), env()) -> env(). -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), Ann = aeso_syntax:get_ann(Type),
NoCode = get_option(no_code, false), NoCode = get_option(no_code, false),
Entry = case X == "init" andalso Env#env.what == contract andalso not NoCode of Entry = if X == "init", What == main_contract, not NoCode ->
true -> {reserved_init, Ann, Type}; {reserved_init, Ann, Type};
false -> {Ann, Type} What == contract -> {contract_fun, Ann, Type};
true -> {Ann, Type}
end, end,
on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) -> on_current_scope(Env, fun(Scope = #scope{ funs = Funs }) ->
Scope#scope{ funs = [{X, Entry} | 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} -> {reserved_init, Ann1, Type} ->
type_error({cannot_call_init_function, Ann}), type_error({cannot_call_init_function, Ann}),
{QName, {Ann1, Type}}; %% Return the type to avoid an extra not-in-scope error {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 -> {Ann1, _} = E ->
%% Check that it's not private (or we can see private funs) %% Check that it's not private (or we can see private funs)
case not is_private(Ann1) orelse AllowPrivate of case not is_private(Ann1) orelse AllowPrivate of
@@ -519,6 +524,7 @@ global_env() ->
%% Conversion %% Conversion
IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) }, IntScope = #scope{ funs = MkDefs([{"to_str", Fun1(Int, String)}]) },
AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)}, AddressScope = #scope{ funs = MkDefs([{"to_str", Fun1(Address, String)},
{"to_contract", FunC(address_to_contract, [Address], A)},
{"is_oracle", Fun1(Address, Bool)}, {"is_oracle", Fun1(Address, Bool)},
{"is_contract", Fun1(Address, Bool)}, {"is_contract", Fun1(Address, Bool)},
{"is_payable", 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) -> infer1(Env, [{contract, Ann, ConName, Code} | Rest], Acc, Options) ->
%% do type inference on each contract independently. %% do type inference on each contract independently.
check_scope_name_clash(Env, contract, ConName), 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}, Contract1 = {contract, Ann, ConName, Code1},
Env2 = pop_scope(Env1), Env2 = pop_scope(Env1),
Env3 = bind_contract(Contract1, Env2), 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), check_scope_name_clash(Env, namespace, Name),
{Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options), {Env1, Code1} = infer_contract_top(push_scope(namespace, Name, Env), namespace, Code, Options),
Namespace1 = {namespace, Ann, Name, Code1}, 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) -> check_scope_name_clash(Env, Kind, Name) ->
case get_scope(Env, qname(Name)) of case get_scope(Env, qname(Name)) of
@@ -602,7 +612,7 @@ check_scope_name_clash(Env, Kind, Name) ->
destroy_and_report_type_errors(Env) destroy_and_report_type_errors(Env)
end. 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()]}. {env(), [aeso_syntax:decl()]}.
infer_contract_top(Env, Kind, Defs0, _Options) -> infer_contract_top(Env, Kind, Defs0, _Options) ->
Defs = desugar(Defs0), 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 %% infer_contract takes a proplist mapping global names to types, and
%% a list of definitions. %% 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) -> infer_contract(Env0, What, Defs) ->
Env = Env0#env{ what = What }, Env = Env0#env{ what = What },
Kind = fun({type_def, _, _, _, _}) -> type; Kind = fun({type_def, _, _, _, _}) -> type;
@@ -625,7 +635,8 @@ infer_contract(Env0, What, Defs) ->
Env2 = Env2 =
case What of case What of
namespace -> Env1; namespace -> Env1;
contract -> bind_state(Env1) %% bind state and put contract -> Env1;
main_contract -> bind_state(Env1) %% bind state and put
end, end,
{ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]), {ProtoSigs, Decls} = lists:unzip([ check_fundecl(Env1, Decl) || Decl <- Get(prototype) ]),
Env3 = bind_funs(ProtoSigs, Env2), Env3 = bind_funs(ProtoSigs, Env2),
@@ -704,18 +715,45 @@ check_unexpected(Xs) ->
check_modifiers(Env, Contracts) -> check_modifiers(Env, Contracts) ->
create_type_errors(), create_type_errors(),
[ case C of check_modifiers_(Env, Contracts),
{contract, _, Con, Decls} -> destroy_and_report_type_errors(Env).
check_modifiers_(Env, [{contract, _, Con, Decls} | Rest]) ->
IsMain = Rest == [],
check_modifiers1(contract, Decls), check_modifiers1(contract, Decls),
case {lists:keymember(letfun, 1, Decls), case {lists:keymember(letfun, 1, Decls),
[ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of [ D || D <- Decls, aeso_syntax:get_ann(entrypoint, D, false) ]} of
{true, []} -> type_error({contract_has_no_entrypoints, Con}); {true, []} -> type_error({contract_has_no_entrypoints, Con});
_ -> ok _ when not IsMain ->
case [ {Ann, Id} || {letfun, Ann, Id, _, _, _} <- Decls ] of
[{Ann, Id} | _] -> type_error({definition_in_non_main_contract, Ann, Id});
[] -> ok
end; end;
{namespace, _, _, Decls} -> check_modifiers1(namespace, Decls); _ -> ok
Decl -> type_error({bad_top_level_decl, Decl}) end,
end || C <- Contracts ], check_modifiers_(Env, Rest);
destroy_and_report_type_errors(Env). 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. -spec check_modifiers1(contract | namespace, [aeso_syntax:decl()] | aeso_syntax:decl()) -> ok.
check_modifiers1(What, Decls) when is_list(Decls) -> check_modifiers1(What, Decls) when is_list(Decls) ->
@@ -783,7 +821,8 @@ check_type(_Env, Type = {uvar, _, _}, Arity) ->
ensure_base_type(Type, Arity), ensure_base_type(Type, Arity),
Type; Type;
check_type(_Env, {args_t, Ann, Ts}, _) -> check_type(_Env, {args_t, Ann, Ts}, _) ->
type_error({new_tuple_syntax, Ann, Ts}). type_error({new_tuple_syntax, Ann, Ts}),
{tuple_t, Ann, Ts}.
ensure_base_type(Type, Arity) -> ensure_base_type(Type, Arity) ->
[ type_error({wrong_type_arguments, Type, Arity, 0}) || Arity /= 0 ], [ type_error({wrong_type_arguments, Type, Arity, 0}) || Arity /= 0 ],
@@ -1080,7 +1119,7 @@ infer_expr(_Env, Body={oracle_query_id, As, _}) ->
infer_expr(_Env, Body={contract_pubkey, As, _}) -> infer_expr(_Env, Body={contract_pubkey, As, _}) ->
Con = fresh_uvar(As), Con = fresh_uvar(As),
constrain([#is_contract_constraint{ contract_t = Con, constrain([#is_contract_constraint{ contract_t = Con,
context = Body }]), context = {contract_literal, Body} }]),
{typed, As, Body, Con}; {typed, As, Body, Con};
infer_expr(_Env, Body={id, As, "_"}) -> infer_expr(_Env, Body={id, As, "_"}) ->
{typed, As, Body, fresh_uvar(As)}; {typed, As, Body, fresh_uvar(As)};
@@ -1155,8 +1194,6 @@ infer_expr(Env, {typed, As, Body, Type}) ->
{typed, _, NewBody, NewType} = check_expr(Env, Body, Type1), {typed, _, NewBody, NewType} = check_expr(Env, Body, Type1),
{typed, As, NewBody, NewType}; {typed, As, NewBody, NewType};
infer_expr(Env, {app, Ann, Fun, Args0}) -> infer_expr(Env, {app, Ann, Fun, Args0}) ->
%% TODO: fix parser to give proper annotation for normal applications!
FunAnn = aeso_syntax:get_ann(Fun),
NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ], NamedArgs = [ Arg || Arg = {named_arg, _, _, _} <- Args0 ],
Args = Args0 -- NamedArgs, Args = Args0 -- NamedArgs,
case aeso_syntax:get_ann(format, Ann) of case aeso_syntax:get_ann(format, Ann) of
@@ -1165,15 +1202,15 @@ infer_expr(Env, {app, Ann, Fun, Args0}) ->
prefix -> prefix ->
infer_op(Env, Ann, Fun, Args, fun infer_prefix/1); infer_op(Env, Ann, Fun, Args, fun infer_prefix/1);
_ -> _ ->
NamedArgsVar = fresh_uvar(FunAnn), NamedArgsVar = fresh_uvar(Ann),
NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ], NamedArgs1 = [ infer_named_arg(Env, NamedArgsVar, Arg) || Arg <- NamedArgs ],
%% TODO: named args constraints %% TODO: named args constraints
NewFun={typed, _, _, FunType} = infer_expr(Env, Fun), NewFun={typed, _, _, FunType} = infer_expr(Env, Fun),
NewArgs = [infer_expr(Env, A) || A <- Args], NewArgs = [infer_expr(Env, A) || A <- Args],
ArgTypes = [T || {typed, _, _, T} <- NewArgs], ArgTypes = [T || {typed, _, _, T} <- NewArgs],
ResultType = fresh_uvar(FunAnn), ResultType = fresh_uvar(Ann),
unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, ResultType}, {infer_app, Fun, Args, FunType, ArgTypes}), unify(Env, FunType, {fun_t, [], NamedArgsVar, ArgTypes, ResultType}, {infer_app, Fun, Args, FunType, ArgTypes}),
{typed, FunAnn, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)} {typed, Ann, {app, Ann, NewFun, NamedArgs1 ++ NewArgs}, dereference(ResultType)}
end; end;
infer_expr(Env, {'if', Attrs, Cond, Then, Else}) -> infer_expr(Env, {'if', Attrs, Cond, Then, Else}) ->
NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}), NewCond = check_expr(Env, Cond, {id, Attrs, "bool"}),
@@ -1333,7 +1370,7 @@ infer_case(Env, Attrs, Pattern, ExprType, Branch, SwitchType) ->
end, end,
NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env#env{ in_pattern = true }), NewEnv = bind_vars([{Var, fresh_uvar(Ann)} || Var = {id, Ann, _} <- Vars], Env#env{ in_pattern = true }),
NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern), NewPattern = {typed, _, _, PatType} = infer_expr(NewEnv, Pattern),
NewBranch = check_expr(NewEnv, Branch, SwitchType), NewBranch = check_expr(NewEnv#env{ in_pattern = false }, Branch, SwitchType),
unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}), unify(Env, PatType, ExprType, {case_pat, Pattern, PatType, ExprType}),
{'case', Attrs, NewPattern, NewBranch}. {'case', Attrs, NewPattern, NewBranch}.
@@ -1654,11 +1691,11 @@ check_record_create_constraints(Env, [C | Cs]) ->
check_is_contract_constraints(_Env, []) -> ok; check_is_contract_constraints(_Env, []) -> ok;
check_is_contract_constraints(Env, [C | Cs]) -> 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)), Type1 = unfold_types_in_type(Env, instantiate(Type)),
case lookup_type(Env, record_type_name(Type1)) of case lookup_type(Env, record_type_name(Type1)) of
{_, {_Ann, {[], {contract_t, _}}}} -> ok; {_, {_Ann, {[], {contract_t, _}}}} -> ok;
_ -> type_error({not_a_contract_type, Type1, Lit}) _ -> type_error({not_a_contract_type, Type1, Cxt})
end, end,
check_is_contract_constraints(Env, Cs). check_is_contract_constraints(Env, Cs).
@@ -1748,7 +1785,7 @@ solve_known_record_types(Env, Constraints) ->
C C
end; end;
_ -> _ ->
type_error({not_a_record_type, RecType, When}), type_error({not_a_record_type, instantiate(RecType), When}),
not_solved not_solved
end end
end end
@@ -2079,6 +2116,9 @@ freshen_type_sig(Ann, TypeSig = {type_sig, _, Constr, _, _, _}) ->
FunT. FunT.
apply_typesig_constraint(_Ann, none, _FunT) -> ok; 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}) -> apply_typesig_constraint(Ann, bytes_concat, {fun_t, _, [], [A, B], C}) ->
add_bytes_constraint({add_bytes, Ann, concat, 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]}}) -> apply_typesig_constraint(Ann, bytes_split, {fun_t, _, [], [C], {tuple_t, _, [A, B]}}) ->
@@ -2177,12 +2217,28 @@ mk_error({not_a_record_type, Type, Why}) ->
Msg = io_lib:format("~s\n", [pp_type("Not a record type: ", Type)]), Msg = io_lib:format("~s\n", [pp_type("Not a record type: ", Type)]),
{Pos, Ctxt} = pp_why_record(Why), {Pos, Ctxt} = pp_why_record(Why),
mk_t_err(Pos, Msg, Ctxt); mk_t_err(Pos, Msg, Ctxt);
mk_error({not_a_contract_type, Type, Lit}) -> mk_error({not_a_contract_type, Type, Cxt}) ->
Msg = io_lib:format("The type ~s is not a contract type\n" Msg =
"when checking that the contract literal at ~s\n~s\n" 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", "has the type\n~s\n",
[pp_type("", Type), pp_loc(Lit), pp_expr(" ", Lit), pp_type(" ", Type)]), [pp_expr(" ", Lit), pp_type(" ", Type)])};
mk_t_err(pos(Lit), Msg); {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}) -> mk_error({non_linear_pattern, Pattern, Nonlinear}) ->
Msg = io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n", Msg = io_lib:format("Repeated name~s ~s in pattern\n~s (at ~s)\n",
[plural("", "s", Nonlinear), string:join(Nonlinear, ", "), [plural("", "s", Nonlinear), string:join(Nonlinear, ", "),
@@ -2344,6 +2400,10 @@ mk_error({contract_has_no_entrypoints, Con}) ->
"contract functions must be declared with the 'entrypoint' keyword instead of\n" "contract functions must be declared with the 'entrypoint' keyword instead of\n"
"'function'.\n", [pp_expr("", Con), pp_loc(Con)]), "'function'.\n", [pp_expr("", Con), pp_loc(Con)]),
mk_t_err(pos(Con), Msg); 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}) -> mk_error({unbound_type, Type}) ->
Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]), Msg = io_lib:format("Unbound type ~s (at ~s).\n", [pp_type("", Type), pp_loc(Type)]),
mk_t_err(pos(Type), Msg); mk_t_err(pos(Type), Msg);
@@ -2359,6 +2419,10 @@ mk_error({cannot_call_init_function, Ann}) ->
Msg = "The 'init' function is called exclusively by the create contract transaction\n" Msg = "The 'init' function is called exclusively by the create contract transaction\n"
"and cannot be called from the contract code.\n", "and cannot be called from the contract code.\n",
mk_t_err(pos(Ann), Msg); 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}) -> mk_error({bad_top_level_decl, Decl}) ->
What = case element(1, Decl) of What = case element(1, Decl) of
letval -> "function or entrypoint"; letval -> "function or entrypoint";
@@ -2383,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(" - ", C), pp_loc(C), pp_type(" - ", A), pp_loc(A),
pp_type(" - ", B), pp_loc(B)]), pp_type(" - ", B), pp_loc(B)]),
mk_t_err(pos(Ann), Msg); 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) -> mk_error(Err) ->
Msg = io_lib:format("Unknown error: ~p\n", [Err]), Msg = io_lib:format("Unknown error: ~p\n", [Err]),
mk_t_err(pos(0, 0), Msg). mk_t_err(pos(0, 0), Msg).
+3 -2
View File
@@ -32,7 +32,7 @@
map_delete | map_member | map_size | string_length | map_delete | map_member | map_size | string_length |
string_concat | bits_set | bits_clear | bits_test | bits_sum | string_concat | bits_set | bits_clear | bits_test | bits_sum |
bits_intersection | bits_union | bits_difference | 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_sha3 | crypto_sha256 | crypto_blake2b |
crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1. crypto_ecverify_secp256k1 | crypto_ecrecover_secp256k1.
@@ -200,7 +200,7 @@ builtins() ->
{"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]}, {"union", 2}, {"difference", 2}, {"none", none}, {"all", none}]},
{["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]}, {["Bytes"], [{"to_int", 1}, {"to_str", 1}, {"concat", 2}, {"split", 1}]},
{["Int"], [{"to_str", 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}} maps:from_list([ {NS ++ [Fun], {MkName(NS, Fun), Arity}}
|| {NS, Funs} <- Scopes, || {NS, Funs} <- Scopes,
@@ -904,6 +904,7 @@ op_builtins() ->
string_length, string_concat, string_sha3, string_sha256, string_blake2b, string_length, string_concat, string_sha3, string_sha256, string_blake2b,
bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union, bits_set, bits_clear, bits_test, bits_sum, bits_intersection, bits_union,
bits_difference, int_to_str, address_to_str, crypto_verify_sig, 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_verify_sig_secp256k1, crypto_sha3, crypto_sha256, crypto_blake2b,
crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1 crypto_ecverify_secp256k1, crypto_ecrecover_secp256k1
]. ].
+3
View File
@@ -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_oracle"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Address", "is_contract"]}, _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", "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_int"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bytes", "to_str"]}, _Icode) -> true; is_builtin_fun({qid, _, ["Bytes", "to_str"]}, _Icode) -> true;
is_builtin_fun({qid, _, ["Bytes", "concat"]}, _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) -> builtin_code(_, {qid, _, ["Address", "is_payable"]}, [Addr], _, _, Icode) ->
prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0}, prim_call(?PRIM_CALL_ADDR_IS_PAYABLE, #integer{value = 0},
[ast_body(Addr, Icode)], [word], word); [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) -> builtin_code(_, {qid, _, ["Bytes", "to_int"]}, [Bytes], _, _, Icode) ->
{typed, _, _, {bytes_t, _, N}} = Bytes, {typed, _, _, {bytes_t, _, N}} = Bytes,
+102
View File
@@ -15,6 +15,7 @@
, create_calldata/3 %% deprecated , create_calldata/3 %% deprecated
, create_calldata/4 , create_calldata/4
, version/0 , version/0
, numeric_version/0
, sophia_type_to_typerep/1 , sophia_type_to_typerep/1
, to_sophia_value/4 %% deprecated, need a backend , to_sophia_value/4 %% deprecated, need a backend
, to_sophia_value/5 , to_sophia_value/5
@@ -22,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").
@@ -65,6 +67,17 @@ version() ->
{ok, list_to_binary(VsnString)} {ok, list_to_binary(VsnString)}
end. 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()]}. -spec file(string()) -> {ok, map()} | {error, [aeso_errors:error()]}.
file(Filename) -> file(Filename) ->
file(Filename, []). file(Filename, []).
@@ -495,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) ->
@@ -546,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) ->
+279 -229
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() }.
@@ -38,84 +42,9 @@
-define(i(X), {immediate, X}). -define(i(X), {immediate, X}).
-define(a, {stack, 0}). -define(a, {stack, 0}).
-define(s, {var, -1}). %% TODO: until we have state support in FATE -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 =:= '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 --------------------------------------------------------------
@@ -124,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.
@@ -139,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) ->
@@ -222,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;
@@ -260,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}) ->
@@ -477,7 +423,7 @@ call_to_scode(Env, CallCode, Args) ->
builtin_to_scode(_Env, get_state, []) -> builtin_to_scode(_Env, get_state, []) ->
[push(?s)]; [push(?s)];
builtin_to_scode(Env, set_state, [_] = Args) -> builtin_to_scode(Env, set_state, [_] = Args) ->
call_to_scode(Env, [aeb_fate_ops:store(?s, ?a), call_to_scode(Env, [{'STORE', ?s, ?a},
tuple(0)], Args); tuple(0)], Args);
builtin_to_scode(Env, chain_event, Args) -> builtin_to_scode(Env, chain_event, Args) ->
call_to_scode(Env, [erlang:apply(aeb_fate_ops, log, lists:duplicate(length(Args), ?a)), call_to_scode(Env, [erlang:apply(aeb_fate_ops, log, lists:duplicate(length(Args), ?a)),
@@ -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(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(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(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) -> 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_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); op_to_scode(crypto_ecverify_secp256k1) -> aeb_fate_ops:ecverify_secp256k1(?a, ?a, ?a, ?a);
@@ -624,7 +571,7 @@ op_to_scode(string_blake2b) -> aeb_fate_ops:blake2b(?a, ?a).
%% PUSH and STORE ?a are the same, so we use STORE to make optimizations %% PUSH and STORE ?a are the same, so we use STORE to make optimizations
%% easier, and specialize to PUSH (which is cheaper) at the end. %% easier, and specialize to PUSH (which is cheaper) at the end.
push(A) -> aeb_fate_ops:store(?a, A). push(A) -> {'STORE', ?a, A}.
tuple(0) -> push(?i({tuple, {}})); tuple(0) -> push(?i({tuple, {}}));
tuple(N) -> aeb_fate_ops:tuple(?a, N). tuple(N) -> aeb_fate_ops:tuple(?a, N).
@@ -647,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)
@@ -683,80 +630,77 @@ 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(I)], Op = [Ind, pp_op(desugar_args(I))],
Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []], Ann = [[" % ", Fmt(In), " -> ", Fmt(Out)] || In ++ Out /= []],
[io_lib:format("~-40s~s\n", [Op, Ann]), [io_lib:format("~-40s~s\n", [Op, Ann]),
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({var, N}) when N < 0 -> io_lib:format("store~p", [-N]); 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_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),
Reads1 =
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} -> Reads;
_ -> ordsets:union(Reads, Rs)
end,
LiveIn = ordsets:intersection(Reads1, WritesIn),
LiveOut = ordsets:intersection(Reads, WritesOut),
Ann1 = #{ live_in => LiveIn, live_out => LiveOut },
ann_reads(Code, Reads1, [{i, Ann1, I} | Acc]);
ann_reads([], Reads, Acc) -> {Acc, Reads}.
%% Instruction attributes: reads, writes and purity (pure means no side-effects ann_live1(_LiveTop, switch_body, LiveOut) ->
%% aside from the reads and writes). 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)
LiveIn = ordsets:union(LiveOut -- [W], Reads),
Ann = #{ live_in => LiveIn, live_out => LiveOut },
{{i, Ann, I}, LiveIn}.
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) -> 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,
@@ -765,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);
@@ -849,31 +794,32 @@ attributes(I) ->
{'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]); {'ECVERIFY_SECP256K1', A, B, C, D} -> Pure(A, [B, C, D]);
{'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]); {'ECRECOVER_SECP256K1', A, B, C} -> Pure(A, [B, C]);
{'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]); {'CONTRACT_TO_ADDRESS', A, B} -> Pure(A, [B]);
{'ADDRESS_TO_CONTRACT', A, B} -> Pure(A, [B]);
{'AUTH_TX_HASH', A} -> Pure(A, []); {'AUTH_TX_HASH', A} -> Pure(A, []);
{'BYTES_TO_INT', A, B} -> Pure(A, [B]); {'BYTES_TO_INT', A, B} -> Pure(A, [B]);
{'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]);
@@ -885,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
@@ -904,14 +850,16 @@ 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),
@@ -922,6 +870,7 @@ independent({i, _, I}, {i, _, J}) ->
if WI == pc; WJ == pc -> false; %% no jumps if WI == pc; WJ == pc -> false; %% no jumps
not (PureI or PureJ) -> false; %% at least one is pure not (PureI or PureJ) -> false; %% at least one is pure
StackI and StackJ -> false; %% cannot both use the stack StackI and StackJ -> false; %% cannot both use the stack
WI == WJ -> false; %% cannot write to the same register
true -> true ->
%% and cannot write to each other's inputs %% and cannot write to each other's inputs
not lists:member(WI, RJ) andalso not lists:member(WI, RJ) andalso
@@ -932,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.
@@ -945,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 --
@@ -977,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).
@@ -989,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)
@@ -1019,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)
]. ].
@@ -1041,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} ->
@@ -1116,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} ->
@@ -1133,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;
@@ -1161,11 +1112,11 @@ 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;
@@ -1173,7 +1124,8 @@ 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('NOT', [false]) -> true;
eval_op(_, _) -> no_eval. %% TODO: bits? eval_op(_, _) -> no_eval. %% TODO: bits?
%% Prune impossible branches from switches %% Prune impossible branches from switches
@@ -1182,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
@@ -1194,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],
@@ -1211,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;
@@ -1220,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
@@ -1236,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
@@ -1248,41 +1253,45 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
Inline = case A of Inline = case A of
{arg, _} -> true; {arg, _} -> true;
?i(_) -> true; ?i(_) -> true;
{store, _} -> true;
_ -> false _ -> 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 ->
{I1, Progress1} =
case op_view(I) of case op_view(I) of
{Op, S, As} -> {Op, S, As} ->
case lists:member(R, As) of case lists:member(R, As) of
true -> true -> {from_op_view(Op, S, lists:map(Inl, As)), true};
Acc1 = [{i, Ann, from_op_view(Op, S, lists:map(Inl, As))} | Acc], false -> {I, Progress}
case r_inline_store(Acc1, R, A, Code) of
false -> {lists:reverse(Acc1), Code};
{_, _} = Res -> Res
end; end;
false -> _ -> {I, Progress}
r_inline_store([{i, Ann, I} | Acc], R, A, Code) end,
end; Acc1 = [{i, Ann, I1} | Acc],
_ -> r_inline_store([{i, Ann, I} | Acc], R, A, Code) %% 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
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]) ->
case op_view(I) of case op_view(I) of
{Op, R, As} -> {Op, R = {var, _}, As} ->
Copy = case J of Copy = case J of
{'STORE', S, R} -> {write_to, S}; {'STORE', S, R} -> {write_to, S};
_ -> false _ -> false
@@ -1299,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
@@ -1312,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
#{ write := dst, read := Rs } -> CheckReads(Rs, {Op, R, As});
#{ write := none, read := Rs } -> CheckReads(Rs, {Op, none, [R | As]});
_ -> false _ -> 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;
@@ -1336,18 +1349,27 @@ unannotate({i, _Ann, I}) -> [I].
%% Desugar and specialize %% Desugar and specialize
desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_ops:inc()]; desugar({'ADD', ?a, ?i(1), ?a}) -> [aeb_fate_ops:inc()];
desugar({'ADD', A, ?i(1), A}) -> [aeb_fate_ops:inc(A)]; desugar({'ADD', A, ?i(1), A}) -> [aeb_fate_ops:inc(desugar_arg(A))];
desugar({'ADD', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:inc()]; desugar({'ADD', ?a, ?a, ?i(1)}) -> [aeb_fate_ops:inc()];
desugar({'ADD', A, A, ?i(1)}) -> [aeb_fate_ops:inc(A)]; 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(A)]; desugar({'SUB', A, A, ?i(1)}) -> [aeb_fate_ops:dec(desugar_arg(A))];
desugar({'STORE', ?a, A}) -> [aeb_fate_ops:push(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, 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;
desugar(Code) when is_list(Code) -> desugar(Code) when is_list(Code) ->
lists:flatmap(fun desugar/1, Code); lists:flatmap(fun desugar/1, Code);
desugar(I) -> [I]. desugar(I) -> [desugar_args(I)].
desugar_args(I) when is_tuple(I) ->
[Op | Args] = tuple_to_list(I),
list_to_tuple([Op | lists:map(fun desugar_arg/1, Args)]);
desugar_args(I) -> I.
desugar_arg({store, N}) -> {var, -N};
desugar_arg(A) -> A.
%% -- Phase III -------------------------------------------------------------- %% -- Phase III --------------------------------------------------------------
%% Constructing basic blocks %% Constructing basic blocks
@@ -1412,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 ->
@@ -1432,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};
@@ -1442,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} ->
case lists:usort(Alts) == [missing] of
true -> {Blk#blk{code = Pop ++ [{jump, DefRef}]}, [], []};
false ->
MkBlk = fun(missing) -> {DefRef, []}; MkBlk = fun(missing) -> {DefRef, []};
(ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef) (ACode) -> FreshBlk(ACode ++ [{jump, RestRef}], DefRef)
end, end,
{AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)), {AltRefs, AltBs} = lists:unzip(lists:map(MkBlk, Alts)),
{Blk#blk{code = []}, [{switch, Arg, AltRefs}], lists:append(AltBs)} {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);
@@ -1469,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) ->
@@ -1507,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),
+35 -6
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);
@@ -87,6 +88,7 @@ decl() ->
, ?RULE(token(payable), keyword(contract), con(), tok('='), maybe_block(decl()), add_modifiers([_1], {contract, _2, _3, _5})) , ?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(namespace), con(), tok('='), maybe_block(decl()), {namespace, _1, _2, _4})
, ?RULE(keyword(include), str(), {include, get_ann(_1), _2}) , ?RULE(keyword(include), str(), {include, get_ann(_1), _2})
, pragma()
%% Type declarations TODO: format annotation for "type bla" vs "type bla()" %% Type declarations TODO: format annotation for "type bla" vs "type bla()"
, ?RULE(keyword(type), id(), {type_decl, _1, _2, []}) , ?RULE(keyword(type), id(), {type_decl, _1, _2, []})
@@ -104,6 +106,16 @@ decl() ->
, ?RULE(keyword('let'), valdef(), set_pos(get_pos(_1), _2)) , ?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() -> fun_or_entry() ->
choice([?RULE(keyword(function), {function, _1}), choice([?RULE(keyword(function), {function, _1}),
?RULE(keyword(entrypoint), {entrypoint, _1})]). ?RULE(keyword(entrypoint), {entrypoint, _1})]).
@@ -312,7 +324,7 @@ map_key(Key, {ok, {_, Val}}) -> {map_key, Key, Val}.
elim(E, []) -> E; elim(E, []) -> E;
elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es); elim(E, [{proj, Ann, P} | Es]) -> elim({proj, Ann, E, P}, Es);
elim(E, [{app, Ann, Args} | Es]) -> elim({app, Ann, E, Args}, Es); elim(E, [{app, _Ann, Args} | Es]) -> elim({app, aeso_syntax:get_ann(E), E, Args}, Es);
elim(E, [{rec_upd, Ann, Flds} | Es]) -> elim(record_update(Ann, E, Flds), Es); elim(E, [{rec_upd, Ann, Flds} | Es]) -> elim(record_update(Ann, E, Flds), Es);
elim(E, [{map_get, Ann, Key} | Es]) -> elim({map_get, Ann, E, Key}, Es); elim(E, [{map_get, Ann, Key} | Es]) -> elim({map_get, Ann, E, Key}, Es);
elim(E, [{map_get, Ann, Key, Val} | Es]) -> elim({map_get, Ann, E, Key, Val}, Es). elim(E, [{map_get, Ann, Key, Val} | Es]) -> elim({map_get, Ann, E, Key, Val}, Es).
@@ -406,7 +418,7 @@ token(Tag) ->
id(Id) -> id(Id) ->
?LET_P({id, A, X} = Y, id(), ?LET_P({id, A, X} = Y, id(),
if X == Id -> Y; if X == Id -> Y;
true -> fail({A, "expected 'bytes'"}) true -> fail({A, "expected '" ++ Id ++ "'"})
end). end).
id_or_addr() -> id_or_addr() ->
@@ -607,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
+5
View File
@@ -149,6 +149,7 @@ decl({contract, _, C, Ds}) ->
block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds)); block(follow(text("contract"), hsep(name(C), text("="))), decls(Ds));
decl({namespace, _, C, Ds}) -> decl({namespace, _, C, Ds}) ->
block(follow(text("namespace"), hsep(name(C), text("="))), decls(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_decl, _, T, Vars}) -> typedecl(alias_t, T, Vars);
decl({type_def, _, T, Vars, Def}) -> decl({type_def, _, T, Vars, Def}) ->
Kind = element(1, Def), Kind = element(1, Def),
@@ -170,6 +171,10 @@ decl(D = {letfun, Attrs, _, _, _, _}) ->
hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]); hsep(lists:map(Mod, Attrs) ++ [letdecl(Fun, D)]);
decl(D = {letval, _, _, _, _}) -> letdecl("let", 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(). -spec expr(aeso_syntax:expr(), options()) -> doc().
expr(E, Options) -> expr(E, Options) ->
with_options(Options, fun() -> expr(E) end). with_options(Options, fun() -> expr(E) end).
+20 -9
View File
@@ -13,14 +13,15 @@
override/2, push/2, pop/1]). override/2, push/2, pop/1]).
lexer() -> lexer() ->
Number = fun(Digit) -> [Digit, "+(_", Digit, "+)*"] end,
DIGIT = "[0-9]", DIGIT = "[0-9]",
HEXDIGIT = "[0-9a-fA-F]", HEXDIGIT = "[0-9a-fA-F]",
LOWER = "[a-z_]", LOWER = "[a-z_]",
UPPER = "[A-Z]", UPPER = "[A-Z]",
CON = [UPPER, "[a-zA-Z0-9_]*"], CON = [UPPER, "[a-zA-Z0-9_]*"],
INT = [DIGIT, "+"], INT = Number(DIGIT),
HEX = ["0x", HEXDIGIT, "+"], HEX = ["0x", Number(HEXDIGIT)],
BYTES = ["#", HEXDIGIT, "+"], BYTES = ["#", Number(HEXDIGIT)],
WS = "[\\000-\\ ]+", WS = "[\\000-\\ ]+",
ID = [LOWER, "[a-zA-Z0-9_']*"], ID = [LOWER, "[a-zA-Z0-9_']*"],
TVAR = ["'", ID], TVAR = ["'", ID],
@@ -53,7 +54,7 @@ lexer() ->
, {CHAR, token(char, fun parse_char/1)} , {CHAR, token(char, fun parse_char/1)}
, {STRING, token(string, fun parse_string/1)} , {STRING, token(string, fun parse_string/1)}
, {HEX, token(hex, fun parse_hex/1)} , {HEX, token(hex, fun parse_hex/1)}
, {INT, token(int, fun list_to_integer/1)} , {INT, token(int, fun parse_int/1)}
, {BYTES, token(bytes, fun parse_bytes/1)} , {BYTES, token(bytes, fun parse_bytes/1)}
%% Identifiers (qualified first!) %% Identifiers (qualified first!)
@@ -95,9 +96,11 @@ parse_char([$', C, $']) -> C.
unescape(Str) -> unescape(Str, []). unescape(Str) -> unescape(Str, []).
%% TODO: numeric escapes
unescape([$"], Acc) -> unescape([$"], Acc) ->
list_to_binary(lists:reverse(Acc)); list_to_binary(lists:reverse(Acc));
unescape([$\\, $x, D1, D2 | Chars ], Acc) ->
C = list_to_integer([D1, D2], 16),
unescape(Chars, [C | Acc]);
unescape([$\\, Code | Chars], Acc) -> unescape([$\\, Code | Chars], Acc) ->
Ok = fun(C) -> unescape(Chars, [C | Acc]) end, Ok = fun(C) -> unescape(Chars, [C | Acc]) end,
case Code of case Code of
@@ -115,10 +118,18 @@ unescape([$\\, Code | Chars], Acc) ->
unescape([C | Chars], Acc) -> unescape([C | Chars], Acc) ->
unescape(Chars, [C | Acc]). unescape(Chars, [C | Acc]).
parse_hex("0x" ++ Chars) -> list_to_integer(Chars, 16). strip_underscores(S) ->
lists:filter(fun(C) -> C /= $_ end, S).
parse_bytes("#" ++ Chars) -> parse_hex("0x" ++ S) ->
N = list_to_integer(Chars, 16), list_to_integer(strip_underscores(S), 16).
Digits = (length(Chars) + 1) div 2,
parse_int(S) ->
list_to_integer(strip_underscores(S)).
parse_bytes("#" ++ S0) ->
S = strip_underscores(S0),
N = list_to_integer(S, 16),
Digits = (length(S) + 1) div 2,
<<N:Digits/unit:8>>. <<N:Digits/unit:8>>.
+6 -1
View File
@@ -13,7 +13,7 @@
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]). -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([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
-export_type([bin_op/0, un_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([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([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]). -export_type([ast/0]).
@@ -36,11 +36,16 @@
-type decl() :: {contract, ann(), con(), [decl()]} -type decl() :: {contract, ann(), con(), [decl()]}
| {namespace, ann(), con(), [decl()]} | {namespace, ann(), con(), [decl()]}
| {pragma, ann(), pragma()}
| {type_decl, ann(), id(), [tvar()]} | {type_decl, ann(), id(), [tvar()]}
| {type_def, ann(), id(), [tvar()], typedef()} | {type_def, ann(), id(), [tvar()], typedef()}
| {fun_decl, ann(), id(), type()} | {fun_decl, ann(), id(), type()}
| letbind(). | letbind().
-type compiler_version() :: [non_neg_integer()].
-type pragma() :: {compiler, '==' | '<' | '>' | '=<' | '>=', compiler_version()}.
-type letbind() -type letbind()
:: {letval, ann(), id(), type(), expr()} :: {letval, ann(), id(), type(), expr()}
| {letfun, ann(), id(), [arg()], type(), expr()}. | {letfun, ann(), id(), [arg()], type(), expr()}.
+26 -4
View File
@@ -18,8 +18,13 @@ 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>>,
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, {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>>,
@@ -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-rc3"}, {vsn, "4.1.0-rc1"},
{registered, []}, {registered, []},
{applications, {applications,
[kernel, [kernel,
+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"
+86 -6
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.
@@ -152,7 +160,8 @@ compilable_contracts() ->
"manual_stdlib_include", "manual_stdlib_include",
"list_comp", "list_comp",
"payable", "payable",
"unapplied_builtins" "unapplied_builtins",
"underscore_number_literals"
]. ].
not_yet_compilable(fate) -> []; not_yet_compilable(fate) -> [];
@@ -175,6 +184,8 @@ not_yet_compilable(aevm) -> [].
-define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)). -define(PARSE_ERROR(Name, Errs), ?ERROR("Parse", Name, Errs)).
failing_contracts() -> failing_contracts() ->
{ok, V} = aeso_compiler:numeric_version(),
Version = list_to_binary(string:join([integer_to_list(N) || N <- V], ".")),
%% Parse errors %% Parse errors
[ ?PARSE_ERROR(field_parse_error, [ ?PARSE_ERROR(field_parse_error,
[<<?Pos(5, 26) [<<?Pos(5, 26)
@@ -301,7 +312,14 @@ failing_contracts() ->
<<?Pos(54, 5) <<?Pos(54, 5)
"Let binding at line 54, column 5 must be followed by an expression">>, "Let binding at line 54, column 5 must be followed by an expression">>,
<<?Pos(58, 5) <<?Pos(58, 5)
"Let binding at line 58, column 5 must be followed by an expression">>]) "Let binding at line 58, column 5 must be followed by an expression">>,
<<?Pos(63, 5)
"Cannot unify int\n"
" and bool\n"
"when checking the type of the expression at line 63, column 5\n"
" id(n) : int\n"
"against the expected type\n"
" bool">>])
, ?TYPE_ERROR(init_type_error, , ?TYPE_ERROR(init_type_error,
[<<?Pos(7, 3) [<<?Pos(7, 3)
"Cannot unify string\n" "Cannot unify string\n"
@@ -348,19 +366,19 @@ failing_contracts() ->
, ?TYPE_ERROR(bad_address_literals, , ?TYPE_ERROR(bad_address_literals,
[<<?Pos(32, 5) [<<?Pos(32, 5)
"The type bytes(32) is not a contract type\n" "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" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" bytes(32)">>, " bytes(32)">>,
<<?Pos(30, 5) <<?Pos(30, 5)
"The type oracle(int, bool) is not a contract type\n" "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" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" oracle(int, bool)">>, " oracle(int, bool)">>,
<<?Pos(28, 5) <<?Pos(28, 5)
"The type address is not a contract type\n" "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" " ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ\n"
"has the type\n" "has the type\n"
" address">>, " address">>,
@@ -432,7 +450,13 @@ failing_contracts() ->
"when checking the type of the expression at line 7, column 5\n" "when checking the type of the expression at line 7, column 5\n"
" ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n" " ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt : address\n"
"against the expected type\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, , ?TYPE_ERROR(stateful,
[<<?Pos(13, 35) [<<?Pos(13, 35)
"Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>, "Cannot reference stateful function Chain.spend (at line 13, column 35)\nin the definition of non-stateful function fail1.">>,
@@ -556,6 +580,21 @@ failing_contracts() ->
"and result types\n" "and result types\n"
" - bytes(20) (at line 18, column 25)\n" " - bytes(20) (at line 18, column 25)\n"
" - 'a (at line 19, column 5)">>]) " - '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). -define(Path(File), "code_errors/" ??File).
@@ -671,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()]}}]).
+2
View File
@@ -11,4 +11,6 @@ contract AddressLiterals =
oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
entrypoint contr() : Remote = entrypoint contr() : Remote =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr_addr() : Remote =
Address.to_contract(addr())
+2
View File
@@ -30,4 +30,6 @@ contract AddressLiterals =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr3() : bytes(32) = entrypoint contr3() : bytes(32) =
ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ
entrypoint contr4() : address =
Address.to_contract(Contract.address)
+2
View File
@@ -15,3 +15,5 @@ contract BasicAuth =
entrypoint to_sign(h : hash, n : int) = entrypoint to_sign(h : hash, n : int) =
Crypto.blake2b((h, n)) Crypto.blake2b((h, n))
entrypoint weird_string() : string =
"\x19Weird String\x42\nMore\n"
+6
View File
@@ -0,0 +1,6 @@
contract Foo =
entrypoint foo : () => int
contract Fail =
entrypoint bad() : int = Foo.foo()
+5
View File
@@ -0,0 +1,5 @@
contract ContractOne =
entrypoint foo() = "foo"
contract ContractTwo =
entrypoint bar() = "bar"
+5
View File
@@ -57,3 +57,8 @@ contract Test =
let f() = 0 let f() = 0
let g() = f() let g() = f()
function id(x : 'a) : 'a = x
entrypoint wrong_return(n : int) : bool =
id(n)
@@ -0,0 +1,13 @@
contract UnderscoreNumberLiterals =
entrypoint ints() : list(int) =
[ 1_999_000_000,
19_99_00_00_00,
0xfff_FFF_010 ]
entrypoint bytes() : list(bytes(4)) =
[ #abcd_ef_00,
#01_02_03_04,
#aaaa_FFFF ]
+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
@@ -0,0 +1,7 @@
@compiler < 1.0
@compiler == 9.9.9
@compiler >= 0.1
@compiler =< 100.0.1
contract Fail =
entrypoint foo() = ()