Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 33229c3513 | |||
| 002e55d529 | |||
| 9b518150c3 | |||
| 67948513d5 | |||
| 08fa372c24 | |||
| 3b0ca28c8e | |||
| 86d7b36ba7 | |||
| 43c8328615 | |||
| c15d411660 | |||
| b902226c26 | |||
| c1e8195fd8 | |||
| d5ff9d4a2f | |||
| c395849684 | |||
| 7bac15949c | |||
| 7b6eba5319 | |||
| 99bb3fe1fb | |||
| 311bf49505 | |||
| 0e3bcba07d | |||
| 699d1f7ab8 | |||
| 1a40a93157 | |||
| c078119bc4 | |||
| 31fd8fe24f | |||
| 9ad8e26e88 | |||
| 5adeb6c93e | |||
| 256df25af4 | |||
| 83abfae32b | |||
| 4ca90feea0 | |||
| 09638daa90 | |||
| d59023a9f4 | |||
| 34b52739fd | |||
| 1c83287d45 | |||
| da92ddbd5d | |||
| c1c169273c |
@@ -1,5 +1,5 @@
|
||||
mkdocs==1.2.4
|
||||
mkdocs==1.4.2
|
||||
mkdocs-simple-hooks==0.1.5
|
||||
mkdocs-material==7.3.6
|
||||
mkdocs-material==9.0.9
|
||||
mike==1.1.2
|
||||
pygments==2.12.0
|
||||
pygments==2.14.0
|
||||
+56
-1
@@ -10,6 +10,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Removed
|
||||
### Fixed
|
||||
|
||||
## [7.4.0]
|
||||
### Changed
|
||||
- Names of lifted lambdas now consist of parent function's name and their
|
||||
position in the source code.
|
||||
### Fixed
|
||||
- Lifted lambdas get their names assigned deterministically.
|
||||
|
||||
## [7.3.0]
|
||||
### Fixed
|
||||
- Fixed a bug with polymorphism that allowed functions with the same name but different type to be considered as implementations for their corresponding interface function.
|
||||
- Fixed a bug in the byte code optimization that incorrectly reordered dependent instructions.
|
||||
|
||||
## [7.2.1]
|
||||
### Fixed
|
||||
- Fixed bugs with the newly added debugging symbols
|
||||
|
||||
## [7.2.0]
|
||||
### Added
|
||||
- Toplevel compile-time constants
|
||||
```
|
||||
namespace N =
|
||||
let nc = 1
|
||||
contract C =
|
||||
let cc = 2
|
||||
```
|
||||
- API functions for encoding/decoding Sophia values to/from FATE.
|
||||
### Removed
|
||||
- Remove the mapping from variables to FATE registers from the compilation output.
|
||||
### Fixed
|
||||
- Warning about unused include when there is no include.
|
||||
|
||||
## [7.1.0]
|
||||
### Added
|
||||
- Options to enable/disable certain optimizations.
|
||||
- The ability to call a different instance of the current contract
|
||||
```
|
||||
contract Main =
|
||||
entrypoint spend(x : int) : int = x
|
||||
entrypoint f(c : Main) : int = c.spend(10)
|
||||
```
|
||||
- Return a mapping from variables to FATE registers in the compilation output.
|
||||
- Hole expression.
|
||||
### Changed
|
||||
- Type definitions serialised to ACI as `typedefs` field instead of `type_defs` to increase compatibility.
|
||||
- Check contracts and entrypoints modifiers when implementing interfaces.
|
||||
- Contracts can no longer be used as namespaces.
|
||||
- Do not show unused stateful warning for functions that call other contracts with a non-zero value argument.
|
||||
### Fixed
|
||||
- Typechecker crashes if Chain.create or Chain.clone are used without arguments.
|
||||
|
||||
## [7.0.1]
|
||||
### Added
|
||||
- Add CONTRIBUTING.md file.
|
||||
@@ -361,7 +411,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Simplify calldata creation - instead of passing a compiled contract, simply
|
||||
pass a (stubbed) contract string.
|
||||
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.0.1...HEAD
|
||||
[Unreleased]: https://github.com/aeternity/aesophia/compare/v7.4.0...HEAD
|
||||
[7.4.0]: https://github.com/aeternity/aesophia/compare/v7.3.0...v7.4.0
|
||||
[7.3.0]: https://github.com/aeternity/aesophia/compare/v7.2.1...v7.3.0
|
||||
[7.2.1]: https://github.com/aeternity/aesophia/compare/v7.2.0...v7.2.1
|
||||
[7.2.0]: https://github.com/aeternity/aesophia/compare/v7.1.0...v7.2.0
|
||||
[7.1.0]: https://github.com/aeternity/aesophia/compare/v7.0.1...v7.1.0
|
||||
[7.0.1]: https://github.com/aeternity/aesophia/compare/v7.0.0...v7.0.1
|
||||
[7.0.0]: https://github.com/aeternity/aesophia/compare/v6.1.0...v7.0.0
|
||||
[6.1.0]: https://github.com/aeternity/aesophia/compare/v6.0.2...v6.1.0
|
||||
|
||||
@@ -10,6 +10,8 @@ The following points should be considered before creating a new PR to the Sophia
|
||||
- If a PR introduces a new feature that is relevant to the users of the language, the [Sophia Features Documentation](docs/sophia_features.md) should be updated to describe the new feature.
|
||||
- If a PR introduces new syntax (e.g. changes in [aeso_syntax.erl](src/aeso_syntax.erl), [aeso_scan.erl](src/aeso_scan.erl), or [aeso_parser.erl](src/aeso_parser.erl)), the [Sophia Syntax Documentation](docs/sophia_syntax.md) should be updated to include the new syntax.
|
||||
- If a PR introduces a new library, the public interface of the new library should be fully documented in the [Sophia Standard Library Documentation](docs/sophia_stdlib.md).
|
||||
- If a PR introduces a new compiler option, the new option should be documented in the file
|
||||
[aeso_compiler.md](docs/aeso_compiler.md).
|
||||
|
||||
### Tests
|
||||
|
||||
|
||||
+2
-2
@@ -67,7 +67,7 @@ generates the following JSON structure representing the contract interface:
|
||||
}
|
||||
]
|
||||
},
|
||||
"type_defs": [
|
||||
"typedefs": [
|
||||
{
|
||||
"name": "answers",
|
||||
"typedef": {
|
||||
@@ -138,7 +138,7 @@ be included inside another contract.
|
||||
state =>
|
||||
#{record =>
|
||||
[#{name => <<"a">>,type => <<"Answers.answers">>}]},
|
||||
type_defs =>
|
||||
typedefs =>
|
||||
[#{name => <<"answers">>,
|
||||
typedef => #{<<"map">> => [<<"string">>,<<"int">>]},
|
||||
vars => []}]}}]}
|
||||
|
||||
@@ -51,6 +51,34 @@ The **pp_** options all print to standard output the following:
|
||||
|
||||
`pp_assembler` - print the generated assembler code
|
||||
|
||||
The option `include_child_contract_symbols` includes the symbols of child contracts functions in the generated fate code. It is turned off by default to avoid making contracts bigger on chain.
|
||||
|
||||
#### Options to control which compiler optimizations should run:
|
||||
|
||||
By default all optimizations are turned on, to disable an optimization, it should be
|
||||
explicitly set to false and passed as a compiler option.
|
||||
|
||||
List of optimizations:
|
||||
|
||||
- optimize_inliner
|
||||
- optimize_inline_local_functions
|
||||
- optimize_bind_subexpressions
|
||||
- optimize_let_floating
|
||||
- optimize_simplifier
|
||||
- optimize_drop_unused_lets
|
||||
- optimize_push_consume
|
||||
- optimize_one_shot_var
|
||||
- optimize_write_to_dead_var
|
||||
- optimize_inline_switch_target
|
||||
- optimize_swap_push
|
||||
- optimize_swap_pop
|
||||
- optimize_swap_write
|
||||
- optimize_constant_propagation
|
||||
- optimize_prune_impossible_branches
|
||||
- optimize_single_successful_branch
|
||||
- optimize_inline_store
|
||||
- optimize_float_switch_bod
|
||||
|
||||
#### check_call(ContractString, Options) -> CheckRet
|
||||
|
||||
Types
|
||||
|
||||
+54
-4
@@ -191,6 +191,17 @@ contract interface X : Z =
|
||||
entrypoint z() = 1
|
||||
```
|
||||
|
||||
#### Adding or removing modifiers
|
||||
|
||||
When a `contract` or a `contract interface` implements another `contract interface`, the `payable` and `stateful` modifiers can be kept or changed, both in the contract and in the entrypoints, according to the following rules:
|
||||
|
||||
1. A `payable` contract or interface can implement a `payable` interface or a non-`payable` interface.
|
||||
2. A non-`payable` contract or interface can only implement a non-`payable` interface, and cannot implement a `payable` interface.
|
||||
3. A `payable` entrypoint can implement a `payable` entrypoint or a non-`payable` entrypoint.
|
||||
4. A non-`payable` entrypoint can only implement a non-`payable` entrypoint, and cannot implement a `payable` entrypoint.
|
||||
5. A non-`stateful` entrypoint can implement a `stateful` entrypoint or a non-`stateful` entrypoint.
|
||||
6. A `stateful` entrypoint can only implement a `stateful` entrypoint, and cannot implement a non-`stateful` entrypoint.
|
||||
|
||||
#### Subtyping and variance
|
||||
|
||||
Subtyping in Sophia follows common rules that take type variance into account. As described by [Wikipedia](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science)),
|
||||
@@ -245,10 +256,10 @@ datatype bi('a) = Bi // bi is bivariant on 'a
|
||||
|
||||
The following facts apply here:
|
||||
|
||||
- `co('a)` is a subtype of `co('b) when `'a` is a subtype of `'b`
|
||||
- `ct('a)` is a subtype of `ct('b) when `'b` is a subtype of `'a`
|
||||
- `in('a)` is a subtype of `in('b) when `'a` is equal to `'b`
|
||||
- `bi('a)` is a subtype of `bi('b) always
|
||||
- `co('a)` is a subtype of `co('b)` when `'a` is a subtype of `'b`
|
||||
- `ct('a)` is a subtype of `ct('b)` when `'b` is a subtype of `'a`
|
||||
- `in('a)` is a subtype of `in('b)` when `'a` is equal to `'b`
|
||||
- `bi('a)` is a subtype of `bi('b)` always
|
||||
|
||||
That altogether induce the following rules of subtyping in Sophia:
|
||||
|
||||
@@ -549,6 +560,45 @@ Sophia has the following types:
|
||||
| oracle_query('a, 'b) | `oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY` |
|
||||
| contract | `ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ` |
|
||||
|
||||
## Hole expression
|
||||
|
||||
Hole expressions, written as `???`, are expressions that are used as a placeholder. During compilation, the compiler will generate a type error indication the type of the hole expression.
|
||||
|
||||
```
|
||||
include "List.aes"
|
||||
contract C =
|
||||
entrypoint f() =
|
||||
List.sum(List.map(???, [1,2,3]))
|
||||
```
|
||||
|
||||
A hole expression found in the example above will generate the error `` Found a hole of type `(int) => int` ``. This says that the compiler expects a function from `int` to `int` in place of the `???` placeholder.
|
||||
|
||||
## Constants
|
||||
|
||||
Constants in Sophia are contract-level bindings that can be used in either contracts or namespaces. The value of a constant can be a literal, another constant, or arithmetic operations applied to other constants. Lists, tuples, maps, and records can also be used to define a constant as long as their elements are also constants.
|
||||
|
||||
The following visibility rules apply to constants:
|
||||
* Constants defined inside a contract are private in that contract. Thus, cannot be accessed through instances of their defining contract.
|
||||
* Constants defined inside a namespace are public. Thus, can be used in other contracts or namespaces.
|
||||
* Constants cannot be defined inside a contract interface.
|
||||
|
||||
When a constant is shadowed, it can be accessed using its qualified name:
|
||||
|
||||
```
|
||||
contract C =
|
||||
let c = 1
|
||||
entrypoint f() =
|
||||
let c = 2
|
||||
c + C.c // the result is 3
|
||||
```
|
||||
|
||||
The name of the constant must be an id; therefore, no pattern matching is allowed when defining a constant:
|
||||
|
||||
```
|
||||
contract C
|
||||
let x::y::_ = [1,2,3] // this will result in an error
|
||||
```
|
||||
|
||||
## Arithmetic
|
||||
|
||||
Sophia integers (`int`) are represented by arbitrary-sized signed words and support the following
|
||||
|
||||
+46
-34
@@ -190,7 +190,7 @@ using the private key of the `owner` account for signing.
|
||||
##### update
|
||||
```
|
||||
AENS.update(owner : address, name : string, expiry : option(Chain.ttl), client_ttl : option(int),
|
||||
new_ptrs : map(string, AENS.pointee), <signature : signature>) : unit
|
||||
new_ptrs : option(map(string, AENS.pointee)), <signature : signature>) : unit
|
||||
```
|
||||
|
||||
Updates the name. If the optional parameters are set to `None` that parameter
|
||||
@@ -470,38 +470,6 @@ Chain.block_height : int"
|
||||
The height of the current block (i.e. the block in which the current call will be included).
|
||||
|
||||
|
||||
##### coinbase
|
||||
```
|
||||
Chain.coinbase : address
|
||||
```
|
||||
|
||||
The address of the account that mined the current block.
|
||||
|
||||
|
||||
##### timestamp
|
||||
```
|
||||
Chain.timestamp : int
|
||||
```
|
||||
|
||||
The timestamp of the current block.
|
||||
|
||||
|
||||
##### difficulty
|
||||
```
|
||||
Chain.difficulty : int
|
||||
```
|
||||
|
||||
The difficulty of the current block.
|
||||
|
||||
|
||||
##### gas
|
||||
```
|
||||
Chain.gas_limit : int
|
||||
```
|
||||
|
||||
The gas limit of the current block.
|
||||
|
||||
|
||||
##### bytecode_hash
|
||||
```
|
||||
Chain.bytecode_hash : 'c => option(hash)
|
||||
@@ -565,6 +533,7 @@ main contract Market =
|
||||
The typechecker must be certain about the created contract's type, so it is
|
||||
worth writing it explicitly as shown in the example.
|
||||
|
||||
|
||||
##### clone
|
||||
```
|
||||
Chain.clone : ( ref : 'c, gas : int, value : int, protected : bool, ...
|
||||
@@ -623,11 +592,54 @@ implementation of the `init` function does not actually return `state`, but
|
||||
calls `put` instead. Moreover, FATE prevents even handcrafted calls to `init`.
|
||||
|
||||
|
||||
##### coinbase
|
||||
```
|
||||
Chain.coinbase : address
|
||||
```
|
||||
|
||||
The address of the account that mined the current block.
|
||||
|
||||
|
||||
##### difficulty
|
||||
```
|
||||
Chain.difficulty : int
|
||||
```
|
||||
|
||||
The difficulty of the current block.
|
||||
|
||||
|
||||
##### event
|
||||
```
|
||||
Chain.event(e : event) : unit
|
||||
```
|
||||
Emits the event. To use this function one needs to define the `event` type as a `datatype` in the contract.
|
||||
|
||||
Emits the event. To use this function one needs to define the `event` type as a
|
||||
`datatype` in the contract.
|
||||
|
||||
|
||||
##### gas\_limit
|
||||
```
|
||||
Chain.gas_limit : int
|
||||
```
|
||||
|
||||
The gas limit of the current block.
|
||||
|
||||
|
||||
##### spend
|
||||
```
|
||||
Chain.spend(to : address, amount : int) : unit
|
||||
```
|
||||
|
||||
Spend `amount` tokens to `to`. Will fail (and abort the contract) if contract
|
||||
doesn't have `amount` tokens to transfer, or, if `to` is not `payable`.
|
||||
|
||||
|
||||
##### timestamp
|
||||
```
|
||||
Chain.timestamp : int
|
||||
```
|
||||
|
||||
The timestamp of the current block (unix time, milliseconds).
|
||||
|
||||
|
||||
### Char
|
||||
|
||||
@@ -104,6 +104,7 @@ Implement ::= ':' Sep1(Con, ',')
|
||||
Decl ::= 'type' Id ['(' TVar* ')'] '=' TypeAlias
|
||||
| 'record' Id ['(' TVar* ')'] '=' RecordType
|
||||
| 'datatype' Id ['(' TVar* ')'] '=' DataType
|
||||
| 'let' Id [':' Type] '=' Expr
|
||||
| (EModifier* 'entrypoint' | FModifier* 'function') Block(FunDecl)
|
||||
| Using
|
||||
|
||||
@@ -238,6 +239,7 @@ Expr ::= '(' LamArgs ')' '=>' Block(Stmt) // Anonymous function (x) => x +
|
||||
| Int | Bytes | String | Char // Literals 123, 0xff, #00abc123, "foo", '%'
|
||||
| AccountAddress | ContractAddress // Chain identifiers
|
||||
| OracleAddress | OracleQueryId // Chain identifiers
|
||||
| '???' // Hole expression 1 + ???
|
||||
|
||||
Generator ::= Pattern '<-' Expr // Generator
|
||||
| 'if' '(' Expr ')' // Guard
|
||||
|
||||
+2
-3
@@ -2,8 +2,7 @@
|
||||
|
||||
{erl_opts, [debug_info]}.
|
||||
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.1.1"}}}
|
||||
, {getopt, "1.0.1"}
|
||||
{deps, [ {aebytecode, {git, "https://github.com/aeternity/aebytecode.git", {tag, "v3.3.0"}}}
|
||||
, {eblake2, "1.0.0"}
|
||||
, {jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.8.0"}}}
|
||||
]}.
|
||||
@@ -14,7 +13,7 @@
|
||||
{base_plt_apps, [erts, kernel, stdlib, crypto, mnesia]}
|
||||
]}.
|
||||
|
||||
{relx, [{release, {aesophia, "7.0.1"},
|
||||
{relx, [{release, {aesophia, "7.4.0"},
|
||||
[aesophia, aebytecode, getopt]},
|
||||
|
||||
{dev_mode, true},
|
||||
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
{"1.2.0",
|
||||
[{<<"aebytecode">>,
|
||||
{git,"https://github.com/aeternity/aebytecode.git",
|
||||
{ref,"8269dbd71e9011921c60141636f1baa270a0e784"}},
|
||||
{ref,"b38349274fc2bed98d7fe86877e6e1a2df302109"}},
|
||||
0},
|
||||
{<<"aeserialization">>,
|
||||
{git,"https://github.com/aeternity/aeserialization.git",
|
||||
{ref,"eb68fe331bd476910394966b7f5ede7a74d37e35"}},
|
||||
{ref,"177bf604b2a05e940f92cf00e96e6e269e708245"}},
|
||||
1},
|
||||
{<<"base58">>,
|
||||
{git,"https://github.com/aeternity/erl-base58.git",
|
||||
@@ -16,7 +16,7 @@
|
||||
{git,"https://github.com/aeternity/enacl.git",
|
||||
{ref,"793ddb502f7fe081302e1c42227dca70b09f8e17"}},
|
||||
2},
|
||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},0},
|
||||
{<<"getopt">>,{pkg,<<"getopt">>,<<"1.0.1">>},1},
|
||||
{<<"jsx">>,
|
||||
{git,"https://github.com/talentdeficit/jsx.git",
|
||||
{ref,"3074d4865b3385a050badf7828ad31490d860df5"}},
|
||||
|
||||
+4
-4
@@ -91,7 +91,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HE
|
||||
{Es, Tdefs1} = lists:partition(FilterT(<<"event">>), Tdefs0),
|
||||
{Ss, Tdefs} = lists:partition(FilterT(<<"state">>), Tdefs1),
|
||||
|
||||
C1 = C0#{type_defs => Tdefs},
|
||||
C1 = C0#{typedefs => Tdefs},
|
||||
|
||||
C2 = case Es of
|
||||
[] -> C1;
|
||||
@@ -111,7 +111,7 @@ encode_contract(Contract = {Head, _, {con, _, Name}, _, _}) when ?IS_CONTRACT_HE
|
||||
encode_contract(Namespace = {namespace, _, {con, _, Name}, _}) ->
|
||||
Tdefs = [ encode_typedef(T) || T <- sort_decls(contract_types(Namespace)) ],
|
||||
#{namespace => #{name => encode_name(Name),
|
||||
type_defs => Tdefs}}.
|
||||
typedefs => Tdefs}}.
|
||||
|
||||
%% Encode a function definition. Currently we are only interested in
|
||||
%% the interface and type.
|
||||
@@ -234,7 +234,7 @@ do_render_aci_json(Json) ->
|
||||
decode_contract(#{contract := #{name := Name,
|
||||
kind := Kind,
|
||||
payable := Payable,
|
||||
type_defs := Ts0,
|
||||
typedefs := Ts0,
|
||||
functions := Fs} = C}) ->
|
||||
MkTDef = fun(N, T) -> #{name => N, vars => [], typedef => T} end,
|
||||
Ts = [ MkTDef(<<"state">>, maps:get(state, C)) || maps:is_key(state, C) ] ++
|
||||
@@ -246,7 +246,7 @@ decode_contract(#{contract := #{name := Name,
|
||||
end,
|
||||
io_lib:format("~s", [Name])," =\n",
|
||||
decode_tdefs(Ts), decode_funcs(Fs)];
|
||||
decode_contract(#{namespace := #{name := Name, type_defs := Ts}}) when Ts /= [] ->
|
||||
decode_contract(#{namespace := #{name := Name, typedefs := Ts}}) when Ts /= [] ->
|
||||
["namespace ", io_lib:format("~s", [Name])," =\n",
|
||||
decode_tdefs(Ts)];
|
||||
decode_contract(_) -> [].
|
||||
|
||||
+615
-278
File diff suppressed because it is too large
Load Diff
+826
-557
File diff suppressed because it is too large
Load Diff
+76
-27
@@ -12,6 +12,8 @@
|
||||
, file/2
|
||||
, from_string/2
|
||||
, check_call/4
|
||||
, decode_value/4
|
||||
, encode_value/4
|
||||
, create_calldata/3
|
||||
, create_calldata/4
|
||||
, version/0
|
||||
@@ -112,10 +114,12 @@ from_string(ContractString, Options) ->
|
||||
|
||||
from_string1(ContractString, Options) ->
|
||||
#{ fcode := FCode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, fcode_env := FCodeEnv
|
||||
, folded_typed_ast := FoldedTypedAst
|
||||
, warnings := Warnings } = string_to_code(ContractString, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, Options),
|
||||
#{ child_con_env := ChildContracts } = FCodeEnv,
|
||||
SavedFreshNames = maps:get(saved_fresh_names, FCodeEnv, #{}),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, FCode, SavedFreshNames, Options),
|
||||
pp_assembler(FateCode, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = version(),
|
||||
@@ -180,30 +184,55 @@ check_call(Source, FunName, Args, Options) ->
|
||||
check_call1(Source, FunName, Args, Options).
|
||||
|
||||
check_call1(ContractString0, FunName, Args, Options) ->
|
||||
case add_extra_call(ContractString0, {call, FunName, Args}, Options) of
|
||||
{ok, CallName, Code} ->
|
||||
{def, _, _, FcodeArgs} = get_call_body(CallName, Code),
|
||||
{ok, FunName, [ aeso_fcode_to_fate:term_to_fate(A) || A <- FcodeArgs ]};
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
add_extra_call(Contract0, Call, Options) ->
|
||||
try
|
||||
%% First check the contract without the __call function
|
||||
#{fcode := OrgFcode
|
||||
, fcode_env := #{child_con_env := ChildContracts}
|
||||
, ast := Ast} = string_to_code(ContractString0, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, []),
|
||||
, ast := Ast} = string_to_code(Contract0, Options),
|
||||
FateCode = aeso_fcode_to_fate:compile(ChildContracts, OrgFcode, #{}, []),
|
||||
%% collect all hashes and compute the first name without hash collision to
|
||||
SymbolHashes = maps:keys(aeb_fate_code:symbols(FateCode)),
|
||||
CallName = first_none_match(?CALL_NAME, SymbolHashes,
|
||||
lists:seq($1, $9) ++ lists:seq($A, $Z) ++ lists:seq($a, $z)),
|
||||
ContractString = insert_call_function(Ast, ContractString0, CallName, FunName, Args),
|
||||
#{fcode := Fcode} = string_to_code(ContractString, Options),
|
||||
CallArgs = arguments_of_body(CallName, FunName, Fcode),
|
||||
|
||||
{ok, FunName, CallArgs}
|
||||
Contract = insert_call_function(Ast, Contract0, CallName, Call),
|
||||
{ok, CallName, string_to_code(Contract, Options)}
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
arguments_of_body(CallName, _FunName, Fcode) ->
|
||||
get_call_body(CallName, #{fcode := Fcode}) ->
|
||||
#{body := Body} = maps:get({entrypoint, list_to_binary(CallName)}, maps:get(functions, Fcode)),
|
||||
{def, _FName, Args} = Body,
|
||||
%% FName is either {entrypoint, list_to_binary(FunName)} or 'init'
|
||||
[ aeso_fcode_to_fate:term_to_fate(A) || A <- Args ].
|
||||
Body.
|
||||
|
||||
encode_value(Contract0, Type, Value, Options) ->
|
||||
case add_extra_call(Contract0, {value, Type, Value}, Options) of
|
||||
{ok, CallName, Code} ->
|
||||
Body = get_call_body(CallName, Code),
|
||||
{ok, aeb_fate_encoding:serialize(aeso_fcode_to_fate:term_to_fate(Body))};
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
decode_value(Contract0, Type, FateValue, Options) ->
|
||||
case add_extra_call(Contract0, {type, Type}, Options) of
|
||||
{ok, CallName, Code} ->
|
||||
#{ unfolded_typed_ast := TypedAst
|
||||
, type_env := TypeEnv} = Code,
|
||||
{ok, _, Type0} = get_decode_type(CallName, TypedAst),
|
||||
Type1 = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||
fate_data_to_sophia_value(Type0, Type1, FateValue);
|
||||
Err = {error, _} ->
|
||||
Err
|
||||
end.
|
||||
|
||||
first_none_match(_CallName, _Hashes, []) ->
|
||||
error(unable_to_find_unique_call_name);
|
||||
@@ -216,14 +245,31 @@ first_none_match(CallName, Hashes, [Char|Chars]) ->
|
||||
end.
|
||||
|
||||
%% Add the __call function to a contract.
|
||||
-spec insert_call_function(aeso_syntax:ast(), string(), string(), string(), [string()]) -> string().
|
||||
insert_call_function(Ast, Code, Call, FunName, Args) ->
|
||||
-spec insert_call_function(aeso_syntax:ast(), string(), string(),
|
||||
{call, string(), [string()]} | {value, string(), string()} | {type, string()}) -> string().
|
||||
insert_call_function(Ast, Code, Call, {call, FunName, Args}) ->
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "),
|
||||
"stateful entrypoint ", Call, "() = ", FunName, "(", string:join(Args, ","), ")\n"
|
||||
]);
|
||||
insert_call_function(Ast, Code, Call, {value, Type, Value}) ->
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "),
|
||||
"entrypoint ", Call, "() : ", Type, " = ", Value, "\n"
|
||||
]);
|
||||
insert_call_function(Ast, Code, Call, {type, Type}) ->
|
||||
Ind = last_contract_indent(Ast),
|
||||
lists:flatten(
|
||||
[ Code,
|
||||
"\n\n",
|
||||
lists:duplicate(Ind, " "),
|
||||
"entrypoint ", Call, "(val : ", Type, ") = val\n"
|
||||
]).
|
||||
|
||||
-spec insert_init_function(string(), options()) -> string().
|
||||
@@ -266,22 +312,25 @@ to_sophia_value(ContractString, FunName, ok, Data, Options0) ->
|
||||
{ok, _, Type0} = get_decode_type(FunName, TypedAst),
|
||||
Type = aeso_ast_infer_types:unfold_types_in_type(TypeEnv, Type0, [unfold_record_types, unfold_variant_types]),
|
||||
|
||||
try
|
||||
{ok, aeso_vm_decode:from_fate(Type, aeb_fate_encoding:deserialize(Data))}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
|
||||
[aeb_fate_encoding:deserialize(Data), Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]};
|
||||
_:_ ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type0)),
|
||||
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end
|
||||
fate_data_to_sophia_value(Type0, Type, Data)
|
||||
catch
|
||||
throw:{error, Errors} -> {error, Errors}
|
||||
end.
|
||||
|
||||
fate_data_to_sophia_value(Type, UnfoldedType, FateData) ->
|
||||
try
|
||||
{ok, aeso_vm_decode:from_fate(UnfoldedType, aeb_fate_encoding:deserialize(FateData))}
|
||||
catch throw:cannot_translate_to_sophia ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type)),
|
||||
Msg = io_lib:format("Cannot translate FATE value ~p\n of Sophia type ~s",
|
||||
[aeb_fate_encoding:deserialize(FateData), Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]};
|
||||
_:_ ->
|
||||
Type1 = prettypr:format(aeso_pretty:type(Type)),
|
||||
Msg = io_lib:format("Failed to decode binary as type ~s", [Type1]),
|
||||
{error, [aeso_errors:new(data_error, Msg)]}
|
||||
end.
|
||||
|
||||
-spec create_calldata(string(), string(), [string()]) ->
|
||||
{ok, binary()} | {error, [aeso_errors:error()]}.
|
||||
create_calldata(Code, Fun, Args) ->
|
||||
|
||||
+288
-159
@@ -9,7 +9,7 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(aeso_fcode_to_fate).
|
||||
|
||||
-export([compile/2, compile/3, term_to_fate/1, term_to_fate/2]).
|
||||
-export([compile/3, compile/4, term_to_fate/1, term_to_fate/2]).
|
||||
|
||||
-ifdef(TEST).
|
||||
-export([optimize_fun/4, to_basic_blocks/1]).
|
||||
@@ -45,7 +45,15 @@
|
||||
-define(s(N), {store, N}).
|
||||
-define(void, {var, 9999}).
|
||||
|
||||
-record(env, { contract, vars = [], locals = [], current_function, tailpos = true, child_contracts = #{}, options = []}).
|
||||
-record(env, { contract,
|
||||
vars = [],
|
||||
locals = [],
|
||||
current_function,
|
||||
tailpos = true,
|
||||
child_contracts = #{},
|
||||
saved_fresh_names = #{},
|
||||
options = [],
|
||||
debug_info = false }).
|
||||
|
||||
%% -- Debugging --------------------------------------------------------------
|
||||
|
||||
@@ -71,16 +79,19 @@ code_error(Err) ->
|
||||
%% -- Main -------------------------------------------------------------------
|
||||
|
||||
%% @doc Main entry point.
|
||||
compile(FCode, Options) ->
|
||||
compile(#{}, FCode, Options).
|
||||
compile(ChildContracts, FCode, Options) ->
|
||||
compile(FCode, SavedFreshNames, Options) ->
|
||||
compile(#{}, FCode, SavedFreshNames, Options).
|
||||
compile(ChildContracts, FCode, SavedFreshNames, Options) ->
|
||||
#{ contract_name := ContractName,
|
||||
functions := Functions } = FCode,
|
||||
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, Options),
|
||||
SFuns = functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options),
|
||||
SFuns1 = optimize_scode(SFuns, Options),
|
||||
FateCode = to_basic_blocks(SFuns1),
|
||||
?debug(compile, Options, "~s\n", [aeb_fate_asm:pp(FateCode)]),
|
||||
FateCode.
|
||||
case proplists:get_value(include_child_contract_symbols, Options, false) of
|
||||
false -> FateCode;
|
||||
true -> add_child_symbols(ChildContracts, FateCode)
|
||||
end.
|
||||
|
||||
make_function_id(X) ->
|
||||
aeb_fate_code:symbol_identifier(make_function_name(X)).
|
||||
@@ -89,21 +100,31 @@ make_function_name(event) -> <<"Chain.event">>;
|
||||
make_function_name({entrypoint, Name}) -> Name;
|
||||
make_function_name({local_fun, Xs}) -> list_to_binary("." ++ string:join(Xs, ".")).
|
||||
|
||||
functions_to_scode(ChildContracts, ContractName, Functions, Options) ->
|
||||
add_child_symbols(ChildContracts, FateCode) ->
|
||||
Funs = lists:flatten([ maps:keys(ChildFuns) || {_, #{functions := ChildFuns}} <- maps:to_list(ChildContracts) ]),
|
||||
Symbols = maps:from_list([ {make_function_id(FName), make_function_name(FName)} || FName <- Funs ]),
|
||||
aeb_fate_code:update_symbols(FateCode, Symbols).
|
||||
|
||||
functions_to_scode(ChildContracts, ContractName, Functions, SavedFreshNames, Options) ->
|
||||
FunNames = maps:keys(Functions),
|
||||
maps:from_list(
|
||||
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, Options)}
|
||||
[ {make_function_name(Name), function_to_scode(ChildContracts, ContractName, FunNames, Name, Attrs, Args, Body, Type, SavedFreshNames, Options)}
|
||||
|| {Name, #{args := Args,
|
||||
body := Body,
|
||||
attrs := Attrs,
|
||||
return := Type}} <- maps:to_list(Functions)]).
|
||||
|
||||
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, Options) ->
|
||||
function_to_scode(ChildContracts, ContractName, Functions, Name, Attrs0, Args, Body, ResType, SavedFreshNames, Options) ->
|
||||
{ArgTypes, ResType1} = typesig_to_scode(Args, ResType),
|
||||
Attrs = Attrs0 -- [stateful], %% Only track private and payable from here.
|
||||
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, Options),
|
||||
Attrs = [ A || A <- Attrs0, A == private orelse A == payable ],
|
||||
Env = init_env(ChildContracts, ContractName, Functions, Name, Args, SavedFreshNames, Options),
|
||||
ArgsNames = [ X || {X, _} <- lists:reverse(Env#env.vars) ],
|
||||
|
||||
%% DBG_LOC is added before the function body to make it possible to break
|
||||
%% at the function signature
|
||||
SCode = to_scode(Env, Body),
|
||||
{Attrs, {ArgTypes, ResType1}, SCode}.
|
||||
DbgSCode = dbg_contract(Env) ++ dbg_loc(Env, Attrs0) ++ dbg_scoped_vars(Env, ArgsNames, SCode),
|
||||
{Attrs, {ArgTypes, ResType1}, DbgSCode}.
|
||||
|
||||
-define(tvars, '$tvars').
|
||||
|
||||
@@ -149,14 +170,16 @@ types_to_scode(Ts) -> lists:map(fun type_to_scode/1, Ts).
|
||||
|
||||
%% -- Environment functions --
|
||||
|
||||
init_env(ChildContracts, ContractName, FunNames, Name, Args, Options) ->
|
||||
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
|
||||
contract = ContractName,
|
||||
child_contracts = ChildContracts,
|
||||
locals = FunNames,
|
||||
current_function = Name,
|
||||
options = Options,
|
||||
tailpos = true }.
|
||||
init_env(ChildContracts, ContractName, FunNames, Name, Args, SavedFreshNames, Options) ->
|
||||
#env{ vars = [ {X, {arg, I}} || {I, {X, _}} <- with_ixs(Args) ],
|
||||
contract = ContractName,
|
||||
child_contracts = ChildContracts,
|
||||
locals = FunNames,
|
||||
current_function = Name,
|
||||
options = Options,
|
||||
tailpos = true,
|
||||
saved_fresh_names = SavedFreshNames,
|
||||
debug_info = proplists:get_value(debug_info, Options, false) }.
|
||||
|
||||
next_var(#env{ vars = Vars }) ->
|
||||
1 + lists:max([-1 | [J || {_, {var, J}} <- Vars]]).
|
||||
@@ -185,9 +208,10 @@ serialize_contract_code(Env, C) ->
|
||||
end,
|
||||
case maps:get(C, Cache, none) of
|
||||
none ->
|
||||
Options = Env#env.options,
|
||||
FCode = maps:get(C, Env#env.child_contracts),
|
||||
FateCode = compile(Env#env.child_contracts, FCode, Options),
|
||||
Options = Env#env.options,
|
||||
SavedFreshNames = Env#env.saved_fresh_names,
|
||||
FCode = maps:get(C, Env#env.child_contracts),
|
||||
FateCode = compile(Env#env.child_contracts, FCode, SavedFreshNames, Options),
|
||||
ByteCode = aeb_fate_code:serialize(FateCode, []),
|
||||
{ok, Version} = aeso_compiler:version(),
|
||||
OriginalSourceCode = proplists:get_value(original_src, Options, ""),
|
||||
@@ -221,44 +245,44 @@ lit_to_fate(Env, L) ->
|
||||
term_to_fate(E) -> term_to_fate(#env{}, #{}, E).
|
||||
term_to_fate(GlobEnv, E) -> term_to_fate(GlobEnv, #{}, E).
|
||||
|
||||
term_to_fate(GlobEnv, _Env, {lit, L}) ->
|
||||
term_to_fate(GlobEnv, _Env, {lit, _, L}) ->
|
||||
lit_to_fate(GlobEnv, L);
|
||||
%% negative literals are parsed as 0 - N
|
||||
term_to_fate(_GlobEnv, _Env, {op, '-', [{lit, {int, 0}}, {lit, {int, N}}]}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {op, _, '-', [{lit, _, {int, 0}}, {lit, _, {int, N}}]}) ->
|
||||
aeb_fate_data:make_integer(-N);
|
||||
term_to_fate(_GlobEnv, _Env, nil) ->
|
||||
term_to_fate(_GlobEnv, _Env, {nil, _}) ->
|
||||
aeb_fate_data:make_list([]);
|
||||
term_to_fate(GlobEnv, Env, {op, '::', [Hd, Tl]}) ->
|
||||
term_to_fate(GlobEnv, Env, {op, _, '::', [Hd, Tl]}) ->
|
||||
%% The Tl will translate into a list, because FATE lists are just lists
|
||||
[term_to_fate(GlobEnv, Env, Hd) | term_to_fate(GlobEnv, Env, Tl)];
|
||||
term_to_fate(GlobEnv, Env, {tuple, As}) ->
|
||||
term_to_fate(GlobEnv, Env, {tuple, _, As}) ->
|
||||
aeb_fate_data:make_tuple(list_to_tuple([ term_to_fate(GlobEnv, Env, A) || A<-As]));
|
||||
term_to_fate(GlobEnv, Env, {con, Ar, I, As}) ->
|
||||
term_to_fate(GlobEnv, Env, {con, _, Ar, I, As}) ->
|
||||
FateAs = [ term_to_fate(GlobEnv, Env, A) || A <- As ],
|
||||
aeb_fate_data:make_variant(Ar, I, list_to_tuple(FateAs));
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, bits_all, []}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, _, bits_all, []}) ->
|
||||
aeb_fate_data:make_bits(-1);
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, bits_none, []}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, _, bits_none, []}) ->
|
||||
aeb_fate_data:make_bits(0);
|
||||
term_to_fate(GlobEnv, _Env, {op, bits_set, [B, I]}) ->
|
||||
term_to_fate(GlobEnv, _Env, {op, _, bits_set, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(GlobEnv, B),
|
||||
J = term_to_fate(GlobEnv, I),
|
||||
{bits, N bor (1 bsl J)};
|
||||
term_to_fate(GlobEnv, _Env, {op, bits_clear, [B, I]}) ->
|
||||
term_to_fate(GlobEnv, _Env, {op, _, bits_clear, [B, I]}) ->
|
||||
{bits, N} = term_to_fate(GlobEnv, B),
|
||||
J = term_to_fate(GlobEnv, I),
|
||||
{bits, N band bnot (1 bsl J)};
|
||||
term_to_fate(GlobEnv, Env, {'let', X, E, Body}) ->
|
||||
term_to_fate(GlobEnv, Env, {'let', _, X, E, Body}) ->
|
||||
Env1 = Env#{ X => term_to_fate(GlobEnv, Env, E) },
|
||||
term_to_fate(GlobEnv, Env1, Body);
|
||||
term_to_fate(_GlobEnv, Env, {var, X}) ->
|
||||
term_to_fate(_GlobEnv, Env, {var, _, X}) ->
|
||||
case maps:get(X, Env, undefined) of
|
||||
undefined -> throw(not_a_fate_value);
|
||||
V -> V
|
||||
end;
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, map_empty, []}) ->
|
||||
term_to_fate(_GlobEnv, _Env, {builtin, _, map_empty, []}) ->
|
||||
aeb_fate_data:make_map(#{});
|
||||
term_to_fate(GlobEnv, Env, {op, map_set, [M, K, V]}) ->
|
||||
term_to_fate(GlobEnv, Env, {op, _, map_set, [M, K, V]}) ->
|
||||
Map = term_to_fate(GlobEnv, Env, M),
|
||||
Map#{term_to_fate(GlobEnv, Env, K) => term_to_fate(GlobEnv, Env, V)};
|
||||
term_to_fate(_GlobEnv, _Env, _) ->
|
||||
@@ -266,52 +290,59 @@ term_to_fate(_GlobEnv, _Env, _) ->
|
||||
|
||||
to_scode(Env, T) ->
|
||||
try term_to_fate(Env, T) of
|
||||
V -> [push(?i(V))]
|
||||
V ->
|
||||
FAnn = element(2, T),
|
||||
[dbg_loc(Env, FAnn), push(?i(V))]
|
||||
catch throw:not_a_fate_value ->
|
||||
to_scode1(Env, T)
|
||||
end.
|
||||
|
||||
to_scode1(Env, {lit, L}) ->
|
||||
[push(?i(lit_to_fate(Env, L)))];
|
||||
to_scode1(Env, {lit, Ann, L}) ->
|
||||
[ dbg_loc(Env, Ann), push(?i(lit_to_fate(Env, L))) ];
|
||||
|
||||
to_scode1(_Env, nil) ->
|
||||
[aeb_fate_ops:nil(?a)];
|
||||
to_scode1(Env, {nil, Ann}) ->
|
||||
[ dbg_loc(Env, Ann), aeb_fate_ops:nil(?a) ];
|
||||
|
||||
to_scode1(Env, {var, X}) ->
|
||||
[push(lookup_var(Env, X))];
|
||||
to_scode1(Env, {var, Ann, X}) ->
|
||||
[ dbg_loc(Env, Ann), push(lookup_var(Env, X)) ];
|
||||
|
||||
to_scode1(Env, {con, Ar, I, As}) ->
|
||||
to_scode1(Env, {con, Ann, Ar, I, As}) ->
|
||||
N = length(As),
|
||||
[[to_scode(notail(Env), A) || A <- As],
|
||||
aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N))];
|
||||
[ dbg_loc(Env, Ann),
|
||||
[to_scode(notail(Env), A) || A <- As],
|
||||
aeb_fate_ops:variant(?a, ?i(Ar), ?i(I), ?i(N)) ];
|
||||
|
||||
to_scode1(Env, {tuple, As}) ->
|
||||
to_scode1(Env, {tuple, Ann, As}) ->
|
||||
N = length(As),
|
||||
[[ to_scode(notail(Env), A) || A <- As ],
|
||||
tuple(N)];
|
||||
[ dbg_loc(Env, Ann),
|
||||
[ to_scode(notail(Env), A) || A <- As ],
|
||||
tuple(N) ];
|
||||
|
||||
to_scode1(Env, {proj, E, I}) ->
|
||||
[to_scode(notail(Env), E),
|
||||
aeb_fate_ops:element_op(?a, ?i(I), ?a)];
|
||||
to_scode1(Env, {proj, Ann, E, I}) ->
|
||||
[ dbg_loc(Env, Ann),
|
||||
to_scode(notail(Env), E),
|
||||
aeb_fate_ops:element_op(?a, ?i(I), ?a) ];
|
||||
|
||||
to_scode1(Env, {set_proj, R, I, E}) ->
|
||||
[to_scode(notail(Env), E),
|
||||
to_scode(notail(Env), R),
|
||||
aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a)];
|
||||
to_scode1(Env, {set_proj, Ann, R, I, E}) ->
|
||||
[ dbg_loc(Env, Ann),
|
||||
to_scode(notail(Env), E),
|
||||
to_scode(notail(Env), R),
|
||||
aeb_fate_ops:setelement(?a, ?i(I), ?a, ?a) ];
|
||||
|
||||
to_scode1(Env, {op, Op, Args}) ->
|
||||
call_to_scode(Env, op_to_scode(Op), Args);
|
||||
to_scode1(Env, {op, Ann, Op, Args}) ->
|
||||
[ dbg_loc(Env, Ann) | call_to_scode(Env, op_to_scode(Op), Args) ];
|
||||
|
||||
to_scode1(Env, {'let', X, {var, Y}, Body}) ->
|
||||
to_scode1(Env, {'let', Ann, X, {var, _, Y}, Body}) ->
|
||||
Env1 = bind_var(X, lookup_var(Env, Y), Env),
|
||||
to_scode(Env1, Body);
|
||||
to_scode1(Env, {'let', X, Expr, Body}) ->
|
||||
[ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], to_scode(Env1, Body)) ];
|
||||
to_scode1(Env, {'let', Ann, X, Expr, Body}) ->
|
||||
{I, Env1} = bind_local(X, Env),
|
||||
[ to_scode(notail(Env), Expr),
|
||||
aeb_fate_ops:store({var, I}, {stack, 0}),
|
||||
to_scode(Env1, Body) ];
|
||||
SCode = [ to_scode(notail(Env), Expr),
|
||||
aeb_fate_ops:store({var, I}, {stack, 0}),
|
||||
to_scode(Env1, Body) ],
|
||||
[ dbg_loc(Env, Ann) | dbg_scoped_vars(Env1, [X], SCode) ];
|
||||
|
||||
to_scode1(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args}) ->
|
||||
to_scode1(Env = #env{ current_function = Fun, tailpos = true, debug_info = false }, {def, Ann, Fun, Args}) ->
|
||||
%% Tail-call to current function, f(e0..en). Compile to
|
||||
%% [ let xi = ei ]
|
||||
%% [ STORE argi xi ]
|
||||
@@ -324,61 +355,62 @@ to_scode1(Env = #env{ current_function = Fun, tailpos = true }, {def, Fun, Args}
|
||||
aeb_fate_ops:store({var, I}, ?a)],
|
||||
{[I | Is], Acc1, Env2}
|
||||
end, {[], [], Env}, Args),
|
||||
[ Code,
|
||||
[ dbg_loc(Env, Ann),
|
||||
Code,
|
||||
[ aeb_fate_ops:store({arg, I}, {var, J})
|
||||
|| {I, J} <- lists:zip(lists:seq(0, length(Vars) - 1),
|
||||
lists:reverse(Vars)) ],
|
||||
loop ];
|
||||
to_scode1(Env, {def, Fun, Args}) ->
|
||||
to_scode1(Env, {def, Ann, Fun, Args}) ->
|
||||
FName = make_function_id(Fun),
|
||||
Lbl = aeb_fate_data:make_string(FName),
|
||||
call_to_scode(Env, local_call(Env, ?i(Lbl)), Args);
|
||||
to_scode1(Env, {funcall, Fun, Args}) ->
|
||||
call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args);
|
||||
[ dbg_loc(Env, Ann) | call_to_scode(Env, local_call(Env, ?i(Lbl)), Args) ];
|
||||
to_scode1(Env, {funcall, Ann, Fun, Args}) ->
|
||||
[ dbg_loc(Env, Ann) | call_to_scode(Env, [to_scode(Env, Fun), local_call(Env, ?a)], Args) ];
|
||||
|
||||
to_scode1(Env, {builtin, B, Args}) ->
|
||||
builtin_to_scode(Env, B, Args);
|
||||
to_scode1(Env, {builtin, Ann, B, Args}) ->
|
||||
[ dbg_loc(Env, Ann) | builtin_to_scode(Env, B, Args) ];
|
||||
|
||||
to_scode1(Env, {remote, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) ->
|
||||
to_scode1(Env, {remote, Ann, ArgsT, RetT, Ct, Fun, [Gas, Value, Protected | Args]}) ->
|
||||
Lbl = make_function_id(Fun),
|
||||
{ArgTypes, RetType0} = typesig_to_scode([{"_", T} || T <- ArgsT], RetT),
|
||||
ArgType = ?i(aeb_fate_data:make_typerep({tuple, ArgTypes})),
|
||||
RetType = ?i(aeb_fate_data:make_typerep(RetType0)),
|
||||
case Protected of
|
||||
{lit, {bool, false}} ->
|
||||
SCode = case Protected of
|
||||
{lit, _, {bool, false}} ->
|
||||
case Gas of
|
||||
{builtin, call_gas_left, _} ->
|
||||
{builtin, _, call_gas_left, _} ->
|
||||
Call = aeb_fate_ops:call_r(?a, Lbl, ArgType, RetType, ?a),
|
||||
call_to_scode(Env, Call, [Ct, Value | Args]);
|
||||
_ ->
|
||||
Call = aeb_fate_ops:call_gr(?a, Lbl, ArgType, RetType, ?a, ?a),
|
||||
call_to_scode(Env, Call, [Ct, Value, Gas | Args])
|
||||
end;
|
||||
{lit, {bool, true}} ->
|
||||
{lit, _, {bool, true}} ->
|
||||
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?i(true)),
|
||||
call_to_scode(Env, Call, [Ct, Value, Gas | Args]);
|
||||
_ ->
|
||||
Call = aeb_fate_ops:call_pgr(?a, Lbl, ArgType, RetType, ?a, ?a, ?a),
|
||||
call_to_scode(Env, Call, [Ct, Value, Gas, Protected | Args])
|
||||
end;
|
||||
end,
|
||||
[ dbg_loc(Env, Ann) | SCode ];
|
||||
|
||||
to_scode1(_Env, {get_state, Reg}) ->
|
||||
[push(?s(Reg))];
|
||||
to_scode1(Env, {set_state, Reg, Val}) ->
|
||||
call_to_scode(Env, [{'STORE', ?s(Reg), ?a},
|
||||
tuple(0)], [Val]);
|
||||
to_scode1(Env, {get_state, Ann, Reg}) ->
|
||||
[ dbg_loc(Env, Ann), push(?s(Reg)) ];
|
||||
to_scode1(Env, {set_state, Ann, Reg, Val}) ->
|
||||
[ dbg_loc(Env, Ann) | call_to_scode(Env, [{'STORE', ?s(Reg), ?a}, tuple(0)], [Val]) ];
|
||||
|
||||
to_scode1(Env, {closure, Fun, FVs}) ->
|
||||
to_scode(Env, {tuple, [{lit, {string, make_function_id(Fun)}}, FVs]});
|
||||
to_scode1(Env, {closure, Ann, Fun, FVs}) ->
|
||||
[ to_scode(Env, {tuple, Ann, [{lit, Ann, {string, make_function_id(Fun)}}, FVs]}) ];
|
||||
|
||||
to_scode1(Env, {switch, Case}) ->
|
||||
split_to_scode(Env, Case).
|
||||
to_scode1(Env, {switch, Ann, Case}) ->
|
||||
[ dbg_loc(Env, Ann) | split_to_scode(Env, Case) ].
|
||||
|
||||
local_call( Env, Fun) when Env#env.tailpos -> aeb_fate_ops:call_t(Fun);
|
||||
local_call(_Env, Fun) -> aeb_fate_ops:call(Fun).
|
||||
local_call( Env = #env{debug_info = false}, Fun) when Env#env.tailpos -> aeb_fate_ops:call_t(Fun);
|
||||
local_call(_Env, Fun) -> aeb_fate_ops:call(Fun).
|
||||
|
||||
split_to_scode(Env, {nosplit, Expr}) ->
|
||||
[switch_body, to_scode(Env, Expr)];
|
||||
split_to_scode(Env, {nosplit, Renames, Expr}) ->
|
||||
[switch_body, dbg_scoped_vars(Env, Renames, to_scode(Env, Expr))];
|
||||
split_to_scode(Env, {split, {tuple, _}, X, Alts}) ->
|
||||
{Def, Alts1} = catchall_to_scode(Env, X, Alts),
|
||||
Arg = lookup_var(Env, X),
|
||||
@@ -602,7 +634,7 @@ builtin_to_scode(Env, chain_bytecode_hash, [_Addr] = Args) ->
|
||||
builtin_to_scode(Env, chain_clone,
|
||||
[InitArgsT, GasCap, Value, Prot, Contract | InitArgs]) ->
|
||||
case GasCap of
|
||||
{builtin, call_gas_left, _} ->
|
||||
{builtin, _, call_gas_left, _} ->
|
||||
call_to_scode(Env, aeb_fate_ops:clone(?a, ?a, ?a, ?a),
|
||||
[Contract, InitArgsT, Value, Prot | InitArgs]
|
||||
);
|
||||
@@ -704,12 +736,83 @@ push(A) -> {'STORE', ?a, A}.
|
||||
tuple(0) -> push(?i({tuple, {}}));
|
||||
tuple(N) -> aeb_fate_ops:tuple(?a, N).
|
||||
|
||||
%% -- Debug info functions --
|
||||
|
||||
dbg_contract(#env{debug_info = false}) ->
|
||||
[];
|
||||
dbg_contract(#env{contract = Contract}) ->
|
||||
[{'DBG_CONTRACT', {immediate, Contract}}].
|
||||
|
||||
dbg_loc(#env{debug_info = false}, _) ->
|
||||
[];
|
||||
dbg_loc(_Env, Ann) ->
|
||||
File = case proplists:get_value(file, Ann, no_file) of
|
||||
no_file -> "";
|
||||
F -> F
|
||||
end,
|
||||
Line = proplists:get_value(line, Ann, undefined),
|
||||
case Line of
|
||||
undefined -> [];
|
||||
_ -> [{'DBG_LOC', {immediate, File}, {immediate, Line}}]
|
||||
end.
|
||||
|
||||
dbg_scoped_vars(#env{debug_info = false}, _, SCode) ->
|
||||
SCode;
|
||||
dbg_scoped_vars(_Env, [], SCode) ->
|
||||
SCode;
|
||||
dbg_scoped_vars(Env, [{SavedVarName, Var} | Rest], SCode) ->
|
||||
dbg_scoped_vars(Env, Rest, dbg_scoped_var(Env, SavedVarName, Var, SCode));
|
||||
dbg_scoped_vars(Env = #env{saved_fresh_names = SavedFreshNames}, [Var | Rest], SCode) ->
|
||||
SavedVarName = maps:get(Var, SavedFreshNames, Var),
|
||||
dbg_scoped_vars(Env, Rest, dbg_scoped_var(Env, SavedVarName, Var, SCode)).
|
||||
|
||||
dbg_scoped_var(Env, SavedVarName, Var, SCode) ->
|
||||
case SavedVarName == "_" orelse is_fresh_name(SavedVarName) of
|
||||
true ->
|
||||
SCode;
|
||||
false ->
|
||||
Register = lookup_var(Env, Var),
|
||||
Def = [{'DBG_DEF', {immediate, SavedVarName}, Register}],
|
||||
Undef = [{'DBG_UNDEF', {immediate, SavedVarName}, Register}],
|
||||
Def ++ dbg_undef(Undef, SCode)
|
||||
end.
|
||||
|
||||
is_fresh_name([$% | _]) ->
|
||||
true;
|
||||
is_fresh_name(_) ->
|
||||
false.
|
||||
|
||||
dbg_undef(_Undef, missing) ->
|
||||
missing;
|
||||
dbg_undef(Undef, loop) ->
|
||||
[Undef, loop];
|
||||
dbg_undef(Undef, switch_body) ->
|
||||
[switch_body, Undef];
|
||||
dbg_undef(Undef, {switch, Arg, Type, Alts, Catch}) ->
|
||||
NewAlts = [ dbg_undef(Undef, Alt) || Alt <- Alts ],
|
||||
NewCatch = dbg_undef(Undef, Catch),
|
||||
NewSwitch = {switch, Arg, Type, NewAlts, NewCatch},
|
||||
NewSwitch;
|
||||
dbg_undef(Undef, SCode) when is_list(SCode) ->
|
||||
lists:droplast(SCode) ++ [dbg_undef(Undef, lists:last(SCode))];
|
||||
dbg_undef(Undef, SCode) when is_tuple(SCode); is_atom(SCode) ->
|
||||
[Mnemonic | _] =
|
||||
case is_tuple(SCode) of
|
||||
true -> tuple_to_list(SCode);
|
||||
false -> [SCode]
|
||||
end,
|
||||
Op = aeb_fate_opcodes:m_to_op(Mnemonic),
|
||||
case aeb_fate_opcodes:end_bb(Op) of
|
||||
true -> [Undef, SCode];
|
||||
false -> [SCode, Undef]
|
||||
end.
|
||||
|
||||
%% -- Phase II ---------------------------------------------------------------
|
||||
%% Optimize
|
||||
|
||||
optimize_scode(Funs, Options) ->
|
||||
maps:map(fun(Name, Def) -> optimize_fun(Funs, Name, Def, Options) end,
|
||||
Funs).
|
||||
Funs).
|
||||
|
||||
flatten(missing) -> missing;
|
||||
flatten(Code) -> lists:map(fun flatten_s/1, lists:flatten(Code)).
|
||||
@@ -839,6 +942,10 @@ attributes(I) ->
|
||||
loop -> Impure(pc, []);
|
||||
switch_body -> Pure(none, []);
|
||||
'RETURN' -> Impure(pc, []);
|
||||
{'DBG_LOC', _, _} -> Impure(none, []);
|
||||
{'DBG_DEF', _, _} -> Impure(none, []);
|
||||
{'DBG_UNDEF', _, _} -> Impure(none, []);
|
||||
{'DBG_CONTRACT', _} -> Impure(none, []);
|
||||
{'RETURNR', A} -> Impure(pc, A);
|
||||
{'CALL', A} -> Impure(?a, [A]);
|
||||
{'CALL_R', A, _, B, C, D} -> Impure(?a, [A, B, C, D]);
|
||||
@@ -1034,11 +1141,16 @@ independent({i, _, I}, {i, _, J}) ->
|
||||
StackI = lists:member(?a, [WI | RI]),
|
||||
StackJ = lists:member(?a, [WJ | RJ]),
|
||||
|
||||
if WI == pc; WJ == pc -> false; %% no jumps
|
||||
not (PureI or PureJ) -> false; %% at least one is pure
|
||||
StackI and StackJ -> false; %% cannot both use the stack
|
||||
WI == WJ -> false; %% cannot write to the same register
|
||||
true ->
|
||||
ReadStoreI = [] /= [ x || {store, _} <- RI ],
|
||||
ReadStoreJ = [] /= [ x || {store, _} <- RJ ],
|
||||
|
||||
if WI == pc; WJ == pc -> false; %% no jumps
|
||||
not (PureI or PureJ) -> false; %% at least one is pure
|
||||
StackI and StackJ -> false; %% cannot both use the stack
|
||||
WI == WJ -> false; %% cannot write to the same register
|
||||
ReadStoreI and not PureJ -> false; %% can't read store/state if other is impure
|
||||
ReadStoreJ and not PureI -> false; %% can't read store/state if other is impure
|
||||
true ->
|
||||
%% and cannot write to each other's inputs
|
||||
not lists:member(WI, RJ) andalso
|
||||
not lists:member(WJ, RI)
|
||||
@@ -1092,7 +1204,8 @@ simpl_top(I, Code, Options) ->
|
||||
simpl_top(0, I, Code, _Options) ->
|
||||
code_error({optimizer_out_of_fuel, I, Code});
|
||||
simpl_top(Fuel, I, Code, Options) ->
|
||||
apply_rules(Fuel, rules(), I, Code, Options).
|
||||
Rules = [R || R = {Rule, _} <- rules(), proplists:get_value(Rule, Options, true)],
|
||||
apply_rules(Fuel, Rules, I, Code, Options).
|
||||
|
||||
apply_rules(Fuel, Rules, I, Code, Options) ->
|
||||
Cons = fun(X, Xs) -> simpl_top(Fuel - 1, X, Xs, Options) end,
|
||||
@@ -1119,29 +1232,29 @@ apply_rules_once([{RName, Rule} | Rules], I, Code) ->
|
||||
-define(RULE(Name), {Name, fun Name/2}).
|
||||
|
||||
merge_rules() ->
|
||||
[?RULE(r_push_consume),
|
||||
?RULE(r_one_shot_var),
|
||||
?RULE(r_write_to_dead_var),
|
||||
?RULE(r_inline_switch_target)
|
||||
[?RULE(optimize_push_consume),
|
||||
?RULE(optimize_one_shot_var),
|
||||
?RULE(optimize_write_to_dead_var),
|
||||
?RULE(optimize_inline_switch_target)
|
||||
].
|
||||
|
||||
rules() ->
|
||||
merge_rules() ++
|
||||
[?RULE(r_swap_push),
|
||||
?RULE(r_swap_pop),
|
||||
?RULE(r_swap_write),
|
||||
?RULE(r_constant_propagation),
|
||||
?RULE(r_prune_impossible_branches),
|
||||
?RULE(r_single_successful_branch),
|
||||
?RULE(r_inline_store),
|
||||
?RULE(r_float_switch_body)
|
||||
[?RULE(optimize_swap_push),
|
||||
?RULE(optimize_swap_pop),
|
||||
?RULE(optimize_swap_write),
|
||||
?RULE(optimize_constant_propagation),
|
||||
?RULE(optimize_prune_impossible_branches),
|
||||
?RULE(optimize_single_successful_branch),
|
||||
?RULE(optimize_inline_store),
|
||||
?RULE(optimize_float_switch_body)
|
||||
].
|
||||
|
||||
%% Removing pushes that are immediately consumed.
|
||||
r_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
|
||||
optimize_push_consume({i, Ann1, {'STORE', ?a, A}}, Code) ->
|
||||
inline_push(Ann1, A, 0, Code, []);
|
||||
%% Writing directly to memory instead of going through the accumulator.
|
||||
r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
optimize_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
IsPush =
|
||||
case op_view(I) of
|
||||
{_, ?a, _} -> true;
|
||||
@@ -1153,7 +1266,7 @@ r_push_consume({i, Ann1, I}, [{i, Ann2, {'STORE', R, ?a}} | Code]) ->
|
||||
end,
|
||||
if IsPush -> {[{i, merge_ann(Ann1, Ann2), setelement(2, I, R)}], Code};
|
||||
true -> false end;
|
||||
r_push_consume(_, _) -> false.
|
||||
optimize_push_consume(_, _) -> false.
|
||||
|
||||
inline_push(Ann, Arg, Stack, [{i, _, switch_body} = AI | Code], Acc) ->
|
||||
{AI1, {i, Ann1, _}} = swap_instrs({i, Ann, {'STORE', ?a, Arg}}, AI),
|
||||
@@ -1186,7 +1299,7 @@ split_stack_arg(N, [A | As], Acc) ->
|
||||
split_stack_arg(N1, As, [A | Acc]).
|
||||
|
||||
%% Move PUSHes past non-stack instructions.
|
||||
r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
optimize_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
case op_view(PushI) of
|
||||
{_, ?a, _} ->
|
||||
case independent(Push, I) of
|
||||
@@ -1197,10 +1310,10 @@ r_swap_push(Push = {i, _, PushI}, [I | Code]) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_swap_push(_, _) -> false.
|
||||
optimize_swap_push(_, _) -> false.
|
||||
|
||||
%% Move non-stack instruction past POPs.
|
||||
r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
optimize_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
case independent(IA, JA) of
|
||||
true ->
|
||||
case {op_view(I), op_view(J)} of
|
||||
@@ -1208,7 +1321,7 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
{_, false} -> false;
|
||||
{{_, IR, IAs}, {_, RJ, JAs}} ->
|
||||
NonStackI = not lists:member(?a, [IR | IAs]),
|
||||
%% RJ /= ?a to not conflict with r_swap_push
|
||||
%% RJ /= ?a to not conflict with optimize_swap_push
|
||||
PopJ = RJ /= ?a andalso lists:member(?a, JAs),
|
||||
case NonStackI andalso PopJ of
|
||||
false -> false;
|
||||
@@ -1219,22 +1332,22 @@ r_swap_pop(IA = {i, _, I}, [JA = {i, _, J} | Code]) ->
|
||||
end;
|
||||
false -> false
|
||||
end;
|
||||
r_swap_pop(_, _) -> false.
|
||||
optimize_swap_pop(_, _) -> false.
|
||||
|
||||
%% Match up writes to variables with instructions further down.
|
||||
r_swap_write(I = {i, _, _}, [J | Code]) ->
|
||||
optimize_swap_write(I = {i, _, _}, [J | Code]) ->
|
||||
case {var_writes(I), independent(I, J)} of
|
||||
{[_], true} ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1], I1, Code);
|
||||
optimize_swap_write([J1], I1, Code);
|
||||
_ -> false
|
||||
end;
|
||||
r_swap_write(_, _) -> false.
|
||||
optimize_swap_write(_, _) -> false.
|
||||
|
||||
r_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
|
||||
optimize_swap_write(Pre, I, [{i, _, switch_body} = J | Code]) ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1 | Pre], I1, Code);
|
||||
r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
optimize_swap_write([J1 | Pre], I1, Code);
|
||||
optimize_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
case apply_rules_once(merge_rules(), I, Code0) of
|
||||
{_Rule, New, Rest} ->
|
||||
{lists:reverse(Pre) ++ New, Rest};
|
||||
@@ -1243,27 +1356,27 @@ r_swap_write(Pre, I, Code0 = [J | Code]) ->
|
||||
false -> false;
|
||||
true ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
r_swap_write([J1 | Pre], I1, Code)
|
||||
optimize_swap_write([J1 | Pre], I1, Code)
|
||||
end
|
||||
end;
|
||||
r_swap_write(_, _, _) -> false.
|
||||
optimize_swap_write(_, _, _) -> false.
|
||||
|
||||
%% Precompute instructions with known values
|
||||
r_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
optimize_constant_propagation(Cons = {i, Ann1, {'CONS', R, X, Xs}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
Store = {i, Ann, {'STORE', S, ?i(false)}},
|
||||
Cons1 = case R of
|
||||
?a -> {i, Ann1, {'CONS', ?void, X, Xs}};
|
||||
_ -> Cons
|
||||
end,
|
||||
{[Cons1, Store], Code};
|
||||
r_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
optimize_constant_propagation(Nil = {i, Ann1, {'NIL', R}}, [{i, Ann, {'IS_NIL', S, R}} | Code]) ->
|
||||
Store = {i, Ann, {'STORE', S, ?i(true)}},
|
||||
Nil1 = case R of
|
||||
?a -> {i, Ann1, {'NIL', ?void}};
|
||||
_ -> Nil
|
||||
end,
|
||||
{[Nil1, Store], Code};
|
||||
r_constant_propagation({i, Ann, I}, Code) ->
|
||||
optimize_constant_propagation({i, Ann, I}, Code) ->
|
||||
case op_view(I) of
|
||||
false -> false;
|
||||
{Op, R, As} ->
|
||||
@@ -1277,7 +1390,7 @@ r_constant_propagation({i, Ann, I}, Code) ->
|
||||
end
|
||||
end
|
||||
end;
|
||||
r_constant_propagation(_, _) -> false.
|
||||
optimize_constant_propagation(_, _) -> false.
|
||||
|
||||
eval_op('ADD', [X, Y]) when is_integer(X), is_integer(Y) -> X + Y;
|
||||
eval_op('SUB', [X, Y]) when is_integer(X), is_integer(Y) -> X - Y;
|
||||
@@ -1296,12 +1409,12 @@ eval_op('NOT', [false]) -> true;
|
||||
eval_op(_, _) -> no_eval. %% TODO: bits?
|
||||
|
||||
%% Prune impossible branches from switches
|
||||
r_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||
optimize_prune_impossible_branches({switch, ?i(V), Type, Alts, missing}, Code) ->
|
||||
case pick_branch(Type, V, Alts) of
|
||||
false -> false;
|
||||
Alt -> {Alt, Code}
|
||||
end;
|
||||
r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
|
||||
optimize_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def}, Code) when V == true; V == false ->
|
||||
Alts1 = [if V -> missing; true -> False end,
|
||||
if V -> True; true -> missing end],
|
||||
case Alts == Alts1 of
|
||||
@@ -1312,7 +1425,7 @@ r_prune_impossible_branches({switch, ?i(V), boolean, [False, True] = Alts, Def},
|
||||
_ -> {[{switch, ?i(V), boolean, Alts1, Def}], Code}
|
||||
end
|
||||
end;
|
||||
r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
|
||||
optimize_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_)}},
|
||||
[{switch, R, Type = {variant, _}, Alts, missing} | Code]) when is_integer(Tag) ->
|
||||
case {R, lists:nth(Tag + 1, Alts)} of
|
||||
{_, missing} ->
|
||||
@@ -1328,7 +1441,7 @@ r_prune_impossible_branches(Variant = {i, _, {'VARIANT', R, ?i(_), ?i(Tag), ?i(_
|
||||
false -> {Alt, Code}
|
||||
end
|
||||
end;
|
||||
r_prune_impossible_branches(_, _) -> false.
|
||||
optimize_prune_impossible_branches(_, _) -> false.
|
||||
|
||||
pick_branch(boolean, V, [False, True]) when V == true; V == false ->
|
||||
Alt = if V -> True; true -> False end,
|
||||
@@ -1341,7 +1454,7 @@ pick_branch(_Type, _V, _Alts) ->
|
||||
|
||||
%% 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) ->
|
||||
optimize_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||
case push_code_out_of_switch([Def | Alts]) of
|
||||
{_, none} -> false;
|
||||
{_, many} -> false;
|
||||
@@ -1349,7 +1462,7 @@ r_single_successful_branch({switch, R, Type, Alts, Def}, Code) ->
|
||||
{[Def1 | Alts1], PushedOut} ->
|
||||
{[{switch, R, Type, Alts1, Def1} | PushedOut], Code}
|
||||
end;
|
||||
r_single_successful_branch(_, _) -> false.
|
||||
optimize_single_successful_branch(_, _) -> false.
|
||||
|
||||
push_code_out_of_switch([]) -> {[], none};
|
||||
push_code_out_of_switch([Alt | Alts]) ->
|
||||
@@ -1385,7 +1498,7 @@ does_abort({switch, _, _, Alts, Def}) ->
|
||||
does_abort(_) -> false.
|
||||
|
||||
%% STORE R A, SWITCH R --> SWITCH A
|
||||
r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def} | Code]) ->
|
||||
optimize_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)) };
|
||||
@@ -1404,18 +1517,18 @@ r_inline_switch_target({i, Ann, {'STORE', R, A}}, [{switch, R, Type, Alts, Def}
|
||||
end;
|
||||
_ -> false %% impossible
|
||||
end;
|
||||
r_inline_switch_target(_, _) -> false.
|
||||
optimize_inline_switch_target(_, _) -> false.
|
||||
|
||||
%% Float switch-body to closest switch
|
||||
r_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
|
||||
optimize_float_switch_body(I = {i, _, _}, [J = {i, _, switch_body} | Code]) ->
|
||||
{J1, I1} = swap_instrs(I, J),
|
||||
{[], [J1, I1 | Code]};
|
||||
r_float_switch_body(_, _) -> false.
|
||||
optimize_float_switch_body(_, _) -> false.
|
||||
|
||||
%% Inline stores
|
||||
r_inline_store({i, _, {'STORE', R, R}}, Code) ->
|
||||
optimize_inline_store({i, _, {'STORE', R, R}}, Code) ->
|
||||
{[], Code};
|
||||
r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
optimize_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
%% Not when A is var unless updating the annotations properly.
|
||||
Inline = case A of
|
||||
{arg, _} -> true;
|
||||
@@ -1423,13 +1536,13 @@ r_inline_store(I = {i, _, {'STORE', R = {var, _}, A}}, Code) ->
|
||||
{store, _} -> true;
|
||||
_ -> false
|
||||
end,
|
||||
if Inline -> r_inline_store([I], false, R, A, Code);
|
||||
if Inline -> optimize_inline_store([I], false, R, A, Code);
|
||||
true -> false end;
|
||||
r_inline_store(_, _) -> false.
|
||||
optimize_inline_store(_, _) -> false.
|
||||
|
||||
r_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
|
||||
r_inline_store([I | Acc], Progress, R, A, Code);
|
||||
r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
optimize_inline_store(Acc, Progress, R, A, [I = {i, _, switch_body} | Code]) ->
|
||||
optimize_inline_store([I | Acc], Progress, R, A, Code);
|
||||
optimize_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
#{ write := W } = attributes(I),
|
||||
Inl = fun(X) when X == R -> A; (X) -> X end,
|
||||
case live_in(R, Ann) of
|
||||
@@ -1449,14 +1562,14 @@ r_inline_store(Acc, Progress, R, A, [{i, Ann, I} | Code]) ->
|
||||
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)
|
||||
false -> optimize_inline_store(Acc1, Progress1, R, A, Code)
|
||||
end
|
||||
end;
|
||||
r_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
|
||||
r_inline_store(_, false, _, _, _) -> false.
|
||||
optimize_inline_store(Acc, true, _, _, Code) -> {lists:reverse(Acc), Code};
|
||||
optimize_inline_store(_, false, _, _, _) -> false.
|
||||
|
||||
%% Shortcut write followed by final read
|
||||
r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
optimize_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
case op_view(I) of
|
||||
{Op, R = {var, _}, As} ->
|
||||
Copy = case J of
|
||||
@@ -1470,11 +1583,11 @@ r_one_shot_var({i, Ann1, I}, [{i, Ann2, J} | Code]) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_one_shot_var(_, _) -> false.
|
||||
optimize_one_shot_var(_, _) -> false.
|
||||
|
||||
%% Remove writes to dead variables
|
||||
r_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
|
||||
r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
optimize_write_to_dead_var({i, _, {'STORE', ?void, ?a}}, _) -> false; %% Avoid looping
|
||||
optimize_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
#{ pure := Pure } = attributes(I),
|
||||
case op_view(I) of
|
||||
{_Op, R, As} when R /= ?a, Pure ->
|
||||
@@ -1487,7 +1600,7 @@ r_write_to_dead_var({i, Ann, I}, Code) ->
|
||||
end;
|
||||
_ -> false
|
||||
end;
|
||||
r_write_to_dead_var(_, _) -> false.
|
||||
optimize_write_to_dead_var(_, _) -> false.
|
||||
|
||||
op_view({'ABORT', R}) -> {'ABORT', none, [R]};
|
||||
op_view({'EXIT', R}) -> {'EXIT', none, [R]};
|
||||
@@ -1557,7 +1670,23 @@ bb(_Name, Code) ->
|
||||
Blocks = lists:flatmap(fun split_calls/1, Blocks1),
|
||||
Labels = maps:from_list([ {Ref, I} || {I, {Ref, _}} <- with_ixs(Blocks) ]),
|
||||
BBs = [ set_labels(Labels, B) || B <- Blocks ],
|
||||
maps:from_list(BBs).
|
||||
maps:from_list(dbg_loc_filter(BBs)).
|
||||
|
||||
%% Filter DBG_LOC instructions to keep one instruction per line
|
||||
dbg_loc_filter(BBs) ->
|
||||
dbg_loc_filter(BBs, [], [], sets:new()).
|
||||
|
||||
dbg_loc_filter([], _, AllBlocks, _) ->
|
||||
lists:reverse(AllBlocks);
|
||||
dbg_loc_filter([{I, []} | Rest], AllOps, AllBlocks, DbgLocs) ->
|
||||
dbg_loc_filter(Rest, [], [{I, lists:reverse(AllOps)} | AllBlocks], DbgLocs);
|
||||
dbg_loc_filter([{I, [Op = {'DBG_LOC', _, _} | Ops]} | Rest], AllOps, AllBlocks, DbgLocs) ->
|
||||
case sets:is_element(Op, DbgLocs) of
|
||||
true -> dbg_loc_filter([{I, Ops} | Rest], AllOps, AllBlocks, DbgLocs);
|
||||
false -> dbg_loc_filter([{I, Ops} | Rest], [Op | AllOps], AllBlocks, sets:add_element(Op, DbgLocs))
|
||||
end;
|
||||
dbg_loc_filter([{I, [Op | Ops]} | Rest], AllOps, AllBlocks, DbgLocs) ->
|
||||
dbg_loc_filter([{I, Ops} | Rest], [Op | AllOps], AllBlocks, DbgLocs).
|
||||
|
||||
%% -- Break up scode into basic blocks --
|
||||
|
||||
|
||||
@@ -359,9 +359,12 @@ exprAtom() ->
|
||||
, ?RULE(tok('['), Expr, binop('..'), Expr, tok(']'), _3(_2, _4))
|
||||
, ?RULE(keyword('('), comma_sep(Expr), tok(')'), tuple_e(_1, _2))
|
||||
, letpat()
|
||||
, hole()
|
||||
])
|
||||
end).
|
||||
|
||||
hole() -> ?RULE(token('???'), {id, get_ann(_1), "???"}).
|
||||
|
||||
comprehension_exp() ->
|
||||
?LAZY_P(choice(
|
||||
[ comprehension_bind()
|
||||
|
||||
+5
-1
@@ -261,6 +261,8 @@ type(Type, Options) ->
|
||||
with_options(Options, fun() -> type(Type) end).
|
||||
|
||||
-spec type(aeso_syntax:type()) -> doc().
|
||||
type(F = {fun_t, _, _, var_args, _}) ->
|
||||
type(setelement(4, F, [var_args]));
|
||||
type({fun_t, _, Named, Args, Ret}) ->
|
||||
follow(hsep(args_type(Named ++ Args), text("=>")), type(Ret));
|
||||
type({type_sig, _, Named, Args, Ret}) ->
|
||||
@@ -288,7 +290,9 @@ type(T = {id, _, _}) -> name(T);
|
||||
type(T = {qid, _, _}) -> name(T);
|
||||
type(T = {con, _, _}) -> name(T);
|
||||
type(T = {qcon, _, _}) -> name(T);
|
||||
type(T = {tvar, _, _}) -> name(T).
|
||||
type(T = {tvar, _, _}) -> name(T);
|
||||
|
||||
type(var_args) -> text("var_args").
|
||||
|
||||
-spec args_type([aeso_syntax:type()]) -> doc().
|
||||
args_type(Args) ->
|
||||
|
||||
+4
-3
@@ -10,10 +10,10 @@
|
||||
|
||||
-export([get_ann/1, get_ann/2, get_ann/3, set_ann/2, qualify/2]).
|
||||
|
||||
-export_type([ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
|
||||
-export_type([ann_file/0, ann_line/0, ann_col/0, ann_origin/0, ann_format/0, ann/0]).
|
||||
-export_type([name/0, id/0, con/0, qid/0, qcon/0, tvar/0, op/0]).
|
||||
-export_type([bin_op/0, un_op/0]).
|
||||
-export_type([decl/0, letbind/0, typedef/0, pragma/0]).
|
||||
-export_type([decl/0, letbind/0, typedef/0, pragma/0, fundecl/0]).
|
||||
-export_type([arg/0, field_t/0, constructor_t/0, named_arg_t/0]).
|
||||
-export_type([type/0, constant/0, expr/0, arg_expr/0, field/1, stmt/0, alt/0, lvalue/0, elim/0, pat/0]).
|
||||
-export_type([ast/0]).
|
||||
@@ -24,8 +24,9 @@
|
||||
-type ann_col() :: integer().
|
||||
-type ann_origin() :: system | user.
|
||||
-type ann_format() :: '?:' | hex | infix | prefix | elif.
|
||||
-type ann_file() :: string() | no_file.
|
||||
|
||||
-type ann() :: [ {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
|
||||
-type ann() :: [ {file, ann_file()} | {line, ann_line()} | {col, ann_col()} | {format, ann_format()} | {origin, ann_origin()}
|
||||
| stateful | private | payable | main | interface | entrypoint].
|
||||
|
||||
-type name() :: string().
|
||||
|
||||
@@ -31,11 +31,13 @@
|
||||
| aeso_syntax:field(aeso_syntax:expr())
|
||||
| aeso_syntax:stmt().
|
||||
fold(Alg = #alg{zero = Zero, plus = Plus, scoped = Scoped}, Fun, K, X) ->
|
||||
ExprKind = if K == bind_expr -> bind_expr; true -> expr end,
|
||||
TypeKind = if K == bind_type -> bind_type; true -> type end,
|
||||
Sum = fun(Xs) -> lists:foldl(Plus, Zero, Xs) end,
|
||||
Same = fun(A) -> fold(Alg, Fun, K, A) end,
|
||||
Decl = fun(D) -> fold(Alg, Fun, decl, D) end,
|
||||
Type = fun(T) -> fold(Alg, Fun, type, T) end,
|
||||
Expr = fun(E) -> fold(Alg, Fun, expr, E) end,
|
||||
Type = fun(T) -> fold(Alg, Fun, TypeKind, T) end,
|
||||
Expr = fun(E) -> fold(Alg, Fun, ExprKind, E) end,
|
||||
BindExpr = fun(P) -> fold(Alg, Fun, bind_expr, P) end,
|
||||
BindType = fun(T) -> fold(Alg, Fun, bind_type, T) end,
|
||||
Top = Fun(K, X),
|
||||
@@ -155,4 +157,3 @@ used(D) ->
|
||||
(_, _) -> #{}
|
||||
end, decl, D)),
|
||||
lists:filter(NotBound, Xs).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{application, aesophia,
|
||||
[{description, "Compiler for Aeternity Sophia language"},
|
||||
{vsn, "7.0.1"},
|
||||
{vsn, "7.4.0"},
|
||||
{registered, []},
|
||||
{applications,
|
||||
[kernel,
|
||||
|
||||
@@ -21,7 +21,7 @@ test_cases(1) ->
|
||||
" payable stateful entrypoint a(i : int) = i+1\n">>,
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>,
|
||||
type_defs => [],
|
||||
typedefs => [],
|
||||
payable => true,
|
||||
kind => contract_main,
|
||||
functions =>
|
||||
@@ -43,7 +43,7 @@ test_cases(2) ->
|
||||
MapACI = #{contract =>
|
||||
#{name => <<"C">>, payable => false,
|
||||
kind => contract_main,
|
||||
type_defs =>
|
||||
typedefs =>
|
||||
[#{name => <<"allan">>,
|
||||
typedef => <<"int">>,
|
||||
vars => []}],
|
||||
@@ -76,7 +76,7 @@ test_cases(3) ->
|
||||
name => <<"C">>, payable => false, kind => contract_main,
|
||||
event => #{variant => [#{<<"SingleEventDefined">> => []}]},
|
||||
state => <<"unit">>,
|
||||
type_defs =>
|
||||
typedefs =>
|
||||
[#{name => <<"bert">>,
|
||||
typedef =>
|
||||
#{variant =>
|
||||
|
||||
+162
-18
@@ -69,6 +69,7 @@ simple_compile_test_() ->
|
||||
[ {"Testing warning messages",
|
||||
fun() ->
|
||||
#{ warnings := Warnings } = compile("warnings", [warn_all]),
|
||||
#{ warnings := [] } = compile("warning_unused_include_no_include", [warn_all]),
|
||||
check_warnings(warnings(), Warnings)
|
||||
end} ] ++
|
||||
[].
|
||||
@@ -203,6 +204,11 @@ compilable_contracts() ->
|
||||
"polymorphism_contract_interface_same_decl_multi_interface",
|
||||
"polymorphism_contract_interface_same_name_same_type",
|
||||
"polymorphism_variance_switching_chain_create",
|
||||
"polymorphism_variance_switching_void_supertype",
|
||||
"polymorphism_variance_switching_unify_with_interface_decls",
|
||||
"polymorphism_preserve_or_add_payable_contract",
|
||||
"polymorphism_preserve_or_add_payable_entrypoint",
|
||||
"polymorphism_preserve_or_remove_stateful_entrypoint",
|
||||
"missing_init_fun_state_unit",
|
||||
"complex_compare_leq",
|
||||
"complex_compare",
|
||||
@@ -215,6 +221,8 @@ compilable_contracts() ->
|
||||
"polymorphic_map_keys",
|
||||
"unapplied_contract_call",
|
||||
"unapplied_named_arg_builtin",
|
||||
"resolve_field_constraint_by_arity",
|
||||
"toplevel_constants",
|
||||
"test" % Custom general-purpose test file. Keep it last on the list.
|
||||
].
|
||||
|
||||
@@ -279,7 +287,11 @@ warnings() ->
|
||||
<<?PosW(48, 5)
|
||||
"Unused return value.">>,
|
||||
<<?PosW(60, 5)
|
||||
"The function `dec` is defined but never used.">>
|
||||
"The function `dec` is defined but never used.">>,
|
||||
<<?PosW(73, 9)
|
||||
"The definition of `const` shadows an older definition at line 70, column 3.">>,
|
||||
<<?PosW(84, 7)
|
||||
"The constant `c` is defined but never used.">>
|
||||
]).
|
||||
|
||||
failing_contracts() ->
|
||||
@@ -651,10 +663,6 @@ failing_contracts() ->
|
||||
[<<?Pos(5, 28)
|
||||
"Invalid call to contract entrypoint `Foo.foo`.\n"
|
||||
"It must be called as `c.foo` for some `c : Foo`.">>])
|
||||
, ?TYPE_ERROR(toplevel_let,
|
||||
[<<?Pos(2, 7)
|
||||
"Toplevel \"let\" definitions are not supported. "
|
||||
"Value `this_is_illegal` could be replaced by 0-argument function.">>])
|
||||
, ?TYPE_ERROR(empty_typedecl,
|
||||
[<<?Pos(2, 8)
|
||||
"Empty type declarations are not supported. "
|
||||
@@ -731,10 +739,22 @@ failing_contracts() ->
|
||||
"Conflicting updates for field 'foo'">>])
|
||||
, ?TYPE_ERROR(factories_type_errors,
|
||||
[<<?Pos(10,18)
|
||||
"Chain.clone requires `ref` named argument of contract type.">>,
|
||||
"Chain.clone requires `ref` named argument of contract type.">>,
|
||||
<<?Pos(11,18)
|
||||
"Cannot unify `(gas : int, value : int, protected : bool) => if(protected, option(void), void)` and `(gas : int, value : int, protected : bool, int, bool) => 'b`\n"
|
||||
"when checking contract construction of type\n (gas : int, value : int, protected : bool) =>\n if(protected, option(void), void) (at line 11, column 18)\nagainst the expected type\n (gas : int, value : int, protected : bool, int, bool) => 'b">>,
|
||||
"Cannot unify `(gas : int, value : int, protected : bool) => if(protected, option(void), void)` and `(gas : int, value : int, protected : bool, int, bool) => if(protected, option(void), void)`\n"
|
||||
"when checking contract construction of type\n"
|
||||
" (gas : int, value : int, protected : bool) =>\n"
|
||||
" if(protected, option(void), void) (at line 11, column 18)\n"
|
||||
"against the expected type\n"
|
||||
" (gas : int, value : int, protected : bool, int, bool) =>\n"
|
||||
" if(protected, option(void), void)">>,
|
||||
<<?Pos(11,18)
|
||||
"Cannot unify `Bakoom` and `Kaboom`\n"
|
||||
"when checking that contract construction of type\n"
|
||||
" Bakoom\n"
|
||||
"arising from resolution of variadic function `Chain.clone`\n"
|
||||
"matches the expected type\n"
|
||||
" Kaboom">>,
|
||||
<<?Pos(12,37)
|
||||
"Cannot unify `int` and `bool`\n"
|
||||
"when checking named argument `gas : int` against inferred type `bool`">>,
|
||||
@@ -840,32 +860,36 @@ failing_contracts() ->
|
||||
<<?Pos(48, 5)
|
||||
"Unused return value.">>,
|
||||
<<?Pos(60, 5)
|
||||
"The function `dec` is defined but never used.">>
|
||||
"The function `dec` is defined but never used.">>,
|
||||
<<?Pos(73, 9)
|
||||
"The definition of `const` shadows an older definition at line 70, column 3.">>,
|
||||
<<?Pos(84, 7)
|
||||
"The constant `c` is defined but never used.">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_contract_interface_recursive,
|
||||
[<<?Pos(1,24)
|
||||
"Trying to implement or extend an undefined interface `Z`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_contract_interface_same_name_different_type,
|
||||
[<<?Pos(4,20)
|
||||
"Unimplemented function `f` from the interface `I1` in the contract `I2`">>])
|
||||
[<<?Pos(5,5)
|
||||
"Cannot unify `char` and `int`\n"
|
||||
"when implementing the entrypoint `f` from the interface `I1`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_contract_missing_implementation,
|
||||
[<<?Pos(4,20)
|
||||
"Unimplemented function `f` from the interface `I1` in the contract `I2`">>
|
||||
"Unimplemented entrypoint `f` from the interface `I1` in the contract `I2`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_contract_same_decl_multi_interface,
|
||||
[<<?Pos(7,10)
|
||||
"Unimplemented function `f` from the interface `J` in the contract `C`">>
|
||||
"Both interfaces `I` and `J` implemented by the contract `C` have a function called `f`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_contract_undefined_interface,
|
||||
[<<?Pos(1,14)
|
||||
"Trying to implement or extend an undefined interface `I`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_contract_same_name_different_type_multi_interface,
|
||||
[<<?Pos(9,5)
|
||||
"Duplicate definitions of `f` at\n"
|
||||
" - line 8, column 5\n"
|
||||
" - line 9, column 5">>
|
||||
[<<?Pos(7,10)
|
||||
"Both interfaces `I` and `J` implemented by the contract `C` have a function called `f`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_contract_interface_undefined_interface,
|
||||
[<<?Pos(1,24)
|
||||
@@ -1124,6 +1148,124 @@ failing_contracts() ->
|
||||
" `oracle(string, (int) => int)`\n"
|
||||
"The response type must not be higher-order (contain function types)">>
|
||||
])
|
||||
, ?TYPE_ERROR(var_args_unify_let,
|
||||
[<<?Pos(3,9)
|
||||
"Cannot infer types for variable argument list.\n"
|
||||
"when checking the type of the pattern `x : 'a` against the expected type `(gas : int, value : int, protected : bool, ref : 'b, var_args) => 'b`">>
|
||||
])
|
||||
, ?TYPE_ERROR(var_args_unify_fun_call,
|
||||
[<<?Pos(6,5)
|
||||
"Cannot infer types for variable argument list.\n"
|
||||
"when checking the application of\n"
|
||||
" `g : (() => 'b) => 'b`\n"
|
||||
"to arguments\n"
|
||||
" `Chain.create : (value : int, var_args) => 'c`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_add_stateful_entrypoint,
|
||||
[<<?Pos(5,25)
|
||||
"`f` cannot be stateful because the entrypoint `f` in the interface `I` is not stateful">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_change_entrypoint_to_function,
|
||||
[<<?Pos(6,14)
|
||||
"`f` must be declared as an entrypoint instead of a function in order to implement the entrypoint `f` from the interface `I`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_non_payable_contract_implement_payable,
|
||||
[<<?Pos(4,10)
|
||||
"Non-payable contract `C` cannot implement payable interface `I`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_non_payable_interface_implement_payable,
|
||||
[<<?Pos(4,20)
|
||||
"Non-payable interface `H` cannot implement payable interface `I`">>
|
||||
])
|
||||
, ?TYPE_ERROR(polymorphism_remove_payable_entrypoint,
|
||||
[<<?Pos(5,16)
|
||||
"`f` must be payable because the entrypoint `f` in the interface `I` is payable">>
|
||||
])
|
||||
, ?TYPE_ERROR(calling_child_contract_entrypoint,
|
||||
[<<?Pos(5,20)
|
||||
"Invalid call to contract entrypoint `F.g`.\n"
|
||||
"It must be called as `c.g` for some `c : F`.">>])
|
||||
, ?TYPE_ERROR(using_contract_as_namespace,
|
||||
[<<?Pos(5,3)
|
||||
"Cannot use undefined namespace F">>])
|
||||
, ?TYPE_ERROR(hole_expression,
|
||||
[<<?Pos(5,13)
|
||||
"Found a hole of type `bool`">>,
|
||||
<<?Pos(6,17)
|
||||
"Found a hole of type `string`">>,
|
||||
<<?Pos(9,37)
|
||||
"Found a hole of type `(int) => int`">>,
|
||||
<<?Pos(13,20)
|
||||
"Found a hole of type `'a`">>
|
||||
])
|
||||
, ?TYPE_ERROR(toplevel_constants_contract_as_namespace,
|
||||
[<<?Pos(5,13)
|
||||
"Invalid use of the contract constant `G.const`.\n"
|
||||
"Toplevel contract constants can only be used in the contracts where they are defined.">>,
|
||||
<<?Pos(10,11)
|
||||
"Record type `G` does not have field `const`">>,
|
||||
<<?Pos(10,11)
|
||||
"Unbound field const">>,
|
||||
<<?Pos(11,11)
|
||||
"Record type `G` does not have field `const`">>,
|
||||
<<?Pos(11,11)
|
||||
"Unbound field const">>
|
||||
])
|
||||
, ?TYPE_ERROR(toplevel_constants_cycles,
|
||||
[<<?Pos(2,21)
|
||||
"Unbound variable `selfcycle`">>,
|
||||
<<?Pos(4,5)
|
||||
"Mutual recursion detected between the constants\n"
|
||||
" - `cycle1` at line 4, column 5\n"
|
||||
" - `cycle2` at line 5, column 5\n"
|
||||
" - `cycle3` at line 6, column 5">>
|
||||
])
|
||||
, ?TYPE_ERROR(toplevel_constants_in_interface,
|
||||
[<<?Pos(2,10)
|
||||
"The name of the compile-time constant cannot have pattern matching">>,
|
||||
<<?Pos(3,5)
|
||||
"Cannot define toplevel constants inside a contract interface">>,
|
||||
<<?Pos(4,5)
|
||||
"Cannot define toplevel constants inside a contract interface">>
|
||||
])
|
||||
, ?TYPE_ERROR(toplevel_constants_invalid_expr,
|
||||
[<<?Pos(10,9)
|
||||
"Invalid expression in the definition of the constant `c01`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(11,9)
|
||||
"Invalid expression in the definition of the constant `c02`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(12,9)
|
||||
"Invalid expression in the definition of the constant `c03`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(13,9)
|
||||
"Invalid expression in the definition of the constant `c04`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(14,9)
|
||||
"Invalid expression in the definition of the constant `c05`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(17,9)
|
||||
"Invalid expression in the definition of the constant `c07`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(18,9)
|
||||
"Invalid expression in the definition of the constant `c08`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(19,9)
|
||||
"Invalid expression in the definition of the constant `c09`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(20,9)
|
||||
"Invalid expression in the definition of the constant `c10`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>,
|
||||
<<?Pos(21,9)
|
||||
"Invalid expression in the definition of the constant `c11`\n"
|
||||
"You can only use the following expressions as constants: literals, lists, tuples, maps, and other constants">>
|
||||
])
|
||||
, ?TYPE_ERROR(toplevel_constants_invalid_id,
|
||||
[<<?Pos(2,9)
|
||||
"The name of the compile-time constant cannot have pattern matching">>,
|
||||
<<?Pos(3,9)
|
||||
"The name of the compile-time constant cannot have pattern matching">>
|
||||
])
|
||||
].
|
||||
|
||||
validation_test_() ->
|
||||
@@ -1171,7 +1313,9 @@ validate(Contract1, Contract2) ->
|
||||
true -> [debug_mode];
|
||||
false -> []
|
||||
end ++
|
||||
[{include, {file_system, [aeso_test_utils:contract_path()]}}]);
|
||||
[ {src_file, lists:concat([Contract2, ".aes"])}
|
||||
, {include, {file_system, [aeso_test_utils:contract_path()]}}
|
||||
]);
|
||||
Error -> print_and_throw(Error)
|
||||
end.
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
namespace Ns =
|
||||
datatype d('a) = D | S(int) | M('a, list('a), int)
|
||||
private function fff() = 123
|
||||
let const = 1
|
||||
|
||||
stateful entrypoint
|
||||
f (1, x) = (_) => x
|
||||
@@ -33,6 +34,8 @@ contract AllSyntax =
|
||||
|
||||
type state = shakespeare(int)
|
||||
|
||||
let cc = "str"
|
||||
|
||||
entrypoint init() = {
|
||||
johann = 1000,
|
||||
wolfgang = -10,
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
contract F =
|
||||
entrypoint g() = 1
|
||||
|
||||
main contract C =
|
||||
entrypoint f() = F.g()
|
||||
@@ -1,16 +1,7 @@
|
||||
|
||||
contract interface Remote =
|
||||
entrypoint up_to : (int) => list(int)
|
||||
entrypoint sum : (list(int)) => int
|
||||
entrypoint some_string : () => string
|
||||
entrypoint pair : (int, string) => int * string
|
||||
entrypoint squares : (int) => list(int * int)
|
||||
entrypoint filter_some : (list(option(int))) => list(int)
|
||||
entrypoint all_some : (list(option(int))) => option(list(int))
|
||||
|
||||
contract ComplexTypes =
|
||||
|
||||
record state = { worker : Remote }
|
||||
record state = { worker : ComplexTypes }
|
||||
|
||||
entrypoint init(worker) = {worker = worker}
|
||||
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
|
||||
// Testing primitives for accessing the block chain environment
|
||||
contract interface Interface =
|
||||
entrypoint contract_address : () => address
|
||||
entrypoint call_origin : () => address
|
||||
entrypoint call_caller : () => address
|
||||
entrypoint call_value : () => int
|
||||
|
||||
contract Environment =
|
||||
|
||||
record state = {remote : Interface}
|
||||
record state = {remote : Environment}
|
||||
|
||||
entrypoint init(remote) = {remote = remote}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
include "List.aes"
|
||||
|
||||
contract C =
|
||||
entrypoint f() =
|
||||
let ??? = true
|
||||
let v = ???
|
||||
let q = v == "str"
|
||||
let xs = [1, 2, 3, 4]
|
||||
switch (List.first(List.map(???, xs)))
|
||||
Some(x) => x + 1
|
||||
None => 0
|
||||
|
||||
function g() = ???
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
stateful entrypoint f() = 1
|
||||
@@ -0,0 +1,6 @@
|
||||
contract interface I =
|
||||
entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
entrypoint init() = ()
|
||||
function f() = 1
|
||||
@@ -0,0 +1,5 @@
|
||||
payable contract interface I =
|
||||
payable entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
entrypoint f() = 123
|
||||
@@ -0,0 +1,8 @@
|
||||
payable contract interface I =
|
||||
payable entrypoint f : () => int
|
||||
|
||||
contract interface H : I =
|
||||
payable entrypoint f : () => int
|
||||
|
||||
payable contract C : H =
|
||||
entrypoint f() = 123
|
||||
@@ -0,0 +1,14 @@
|
||||
contract interface F =
|
||||
entrypoint f : () => int
|
||||
|
||||
payable contract interface G : F =
|
||||
payable entrypoint f : () => int
|
||||
entrypoint g : () => int
|
||||
|
||||
payable contract interface H =
|
||||
payable entrypoint h : () => int
|
||||
|
||||
payable contract C : G, H =
|
||||
payable entrypoint f() = 1
|
||||
payable entrypoint g() = 2
|
||||
payable entrypoint h() = 3
|
||||
@@ -0,0 +1,7 @@
|
||||
contract interface I =
|
||||
payable entrypoint f : () => int
|
||||
entrypoint g : () => int
|
||||
|
||||
contract C : I =
|
||||
payable entrypoint f() = 1
|
||||
payable entrypoint g() = 2
|
||||
@@ -0,0 +1,7 @@
|
||||
contract interface I =
|
||||
stateful entrypoint f : () => int
|
||||
stateful entrypoint g : () => int
|
||||
|
||||
contract C : I =
|
||||
stateful entrypoint f() = 1
|
||||
entrypoint g() = 2
|
||||
@@ -0,0 +1,5 @@
|
||||
contract interface I =
|
||||
payable entrypoint f : () => int
|
||||
|
||||
contract C : I =
|
||||
entrypoint f() = 1
|
||||
@@ -0,0 +1,5 @@
|
||||
payable contract interface SalesOffer =
|
||||
entrypoint init : (address, address, int, int) => unit
|
||||
|
||||
payable contract Test : SalesOffer =
|
||||
entrypoint init(_, _, _, _) = ()
|
||||
@@ -0,0 +1,5 @@
|
||||
payable contract interface SalesOffer =
|
||||
entrypoint init : (address, address, int, int) => void
|
||||
|
||||
payable contract Test : SalesOffer =
|
||||
entrypoint init(_ : address, _ : address, _ : int, _ : int) = ()
|
||||
@@ -0,0 +1,9 @@
|
||||
contract First =
|
||||
entrypoint print_num(x) = 1 + x
|
||||
|
||||
contract Second =
|
||||
entrypoint print_num() = 1
|
||||
|
||||
main contract Test =
|
||||
entrypoint f(c) = c.print_num(1)
|
||||
entrypoint g(c) = c.print_num()
|
||||
@@ -1,7 +1,4 @@
|
||||
|
||||
contract interface SpendContract =
|
||||
entrypoint withdraw : (int) => int
|
||||
|
||||
contract SpendTest =
|
||||
|
||||
stateful entrypoint spend(to, amount) =
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
namespace N0 =
|
||||
let nsconst = 1
|
||||
|
||||
namespace N =
|
||||
let nsconst = N0.nsconst
|
||||
|
||||
contract C =
|
||||
datatype event = EventX(int, string)
|
||||
|
||||
record account = { name : string,
|
||||
balance : int }
|
||||
|
||||
let c01 = 2425
|
||||
let c02 = -5
|
||||
let c03 = ak_2gx9MEFxKvY9vMG5YnqnXWv1hCsX7rgnfvBLJS4aQurustR1rt
|
||||
let c04 = true
|
||||
let c05 = Bits.none
|
||||
let c06 = #fedcba9876543210
|
||||
let c07 = "str"
|
||||
let c08 = [1, 2, 3]
|
||||
let c09 = [(true, 24), (false, 19), (false, -42)]
|
||||
let c10 = (42, "Foo", true)
|
||||
let c11 = { name = "str", balance = 100000000 }
|
||||
let c12 = {["foo"] = 19, ["bar"] = 42}
|
||||
let c13 = Some(42)
|
||||
let c14 = 11 : int
|
||||
let c15 = EventX(0, "Hello")
|
||||
let c16 = #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
|
||||
let c17 = #000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
|
||||
let c18 = RelativeTTL(50)
|
||||
let c19 = ok_2YNyxd6TRJPNrTcEDCe9ra59SVUdp9FR9qWC5msKZWYD9bP9z5
|
||||
let c20 = oq_2oRvyowJuJnEkxy58Ckkw77XfWJrmRgmGaLzhdqb67SKEL1gPY
|
||||
let c21 = ct_Ez6MyeTMm17YnTnDdHTSrzMEBKmy7Uz2sXu347bTDPgVH2ifJ : C
|
||||
let c22 = N.nsconst
|
||||
let c23 = c01
|
||||
let c24 = c11.name
|
||||
let c25 : int = 1
|
||||
|
||||
entrypoint f01() = c01
|
||||
entrypoint f02() = c02
|
||||
entrypoint f03() = c03
|
||||
entrypoint f04() = c04
|
||||
entrypoint f05() = c05
|
||||
entrypoint f06() = c06
|
||||
entrypoint f07() = c07
|
||||
entrypoint f08() = c08
|
||||
entrypoint f09() = c09
|
||||
entrypoint f10() = c10
|
||||
entrypoint f11() = c11
|
||||
entrypoint f12() = c12
|
||||
entrypoint f13() = c13
|
||||
entrypoint f14() = c14
|
||||
entrypoint f15() = c15
|
||||
entrypoint f16() = c16
|
||||
entrypoint f17() = c17
|
||||
entrypoint f18() = c18
|
||||
entrypoint f19() = c19
|
||||
entrypoint f20() = c20
|
||||
entrypoint f21() = c21
|
||||
entrypoint f22() = c22
|
||||
entrypoint f23() = c23
|
||||
entrypoint f24() = c24
|
||||
entrypoint f25() = c25
|
||||
entrypoint fqual() = C.c01
|
||||
@@ -0,0 +1,11 @@
|
||||
contract G =
|
||||
let const = 1
|
||||
|
||||
main contract C =
|
||||
let c = G.const
|
||||
|
||||
stateful entrypoint f() =
|
||||
let g = Chain.create() : G
|
||||
|
||||
g.const
|
||||
g.const()
|
||||
@@ -0,0 +1,6 @@
|
||||
contract C =
|
||||
let selfcycle = selfcycle
|
||||
|
||||
let cycle1 = cycle2
|
||||
let cycle2 = cycle3
|
||||
let cycle3 = cycle1
|
||||
@@ -0,0 +1,7 @@
|
||||
contract interface I =
|
||||
let (x::y::_) = [1,2,3]
|
||||
let c = 10
|
||||
let d = 10
|
||||
|
||||
contract C =
|
||||
entrypoint init() = ()
|
||||
@@ -0,0 +1,21 @@
|
||||
main contract C =
|
||||
record account = { name : string,
|
||||
balance : int }
|
||||
|
||||
let one = 1
|
||||
let opt = Some(5)
|
||||
let acc = { name = "str", balance = 100000 }
|
||||
let mpp = {["foo"] = 19, ["bar"] = 42}
|
||||
|
||||
let c01 = [x | x <- [1,2,3,4,5]]
|
||||
let c02 = [x + k | x <- [1,2,3,4,5], let k = x*x]
|
||||
let c03 = [x + y | x <- [1,2,3,4,5], let k = x*x, if (k > 5), y <- [k, k+1, k+2]]
|
||||
let c04 = if (one > 2) 3 else 4
|
||||
let c05 = switch (opt)
|
||||
Some(x) => x
|
||||
None => 2
|
||||
let c07 = acc{ balance = one }
|
||||
let c08 = mpp["foo"]
|
||||
let c09 = mpp["non" = 10]
|
||||
let c10 = mpp{["foo"] = 20}
|
||||
let c11 = (x) => x + 1
|
||||
@@ -0,0 +1,3 @@
|
||||
contract C =
|
||||
let x::_ = [1,2,3,4]
|
||||
let y::(p = z::_) = [1,2,3,4]
|
||||
@@ -1,3 +0,0 @@
|
||||
contract C =
|
||||
let this_is_illegal = 2/0
|
||||
entrypoint this_is_legal() = 2/0
|
||||
@@ -0,0 +1,7 @@
|
||||
contract F =
|
||||
entrypoint g() = 1
|
||||
|
||||
main contract C =
|
||||
using F for [g]
|
||||
|
||||
entrypoint f() = g()
|
||||
@@ -0,0 +1,7 @@
|
||||
contract C =
|
||||
stateful function g(h) =
|
||||
h()
|
||||
|
||||
stateful entrypoint f() =
|
||||
g(Chain.create)
|
||||
123
|
||||
@@ -0,0 +1,4 @@
|
||||
main contract C =
|
||||
stateful entrypoint f() =
|
||||
let x = Chain.clone
|
||||
123
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace N =
|
||||
function nconst() = 1
|
||||
|
||||
main contract C =
|
||||
entrypoint f() = N.nconst()
|
||||
@@ -12,7 +12,7 @@ namespace UnusedNamespace =
|
||||
// Unused
|
||||
private function h() = 3
|
||||
|
||||
contract Warnings =
|
||||
main contract Warnings =
|
||||
|
||||
type state = int
|
||||
|
||||
@@ -58,3 +58,31 @@ namespace FunctionsAsArgs =
|
||||
private function inc(n : int) : int = n + 1
|
||||
// Never used
|
||||
private function dec(n : int) : int = n - 1
|
||||
|
||||
contract Remote =
|
||||
entrypoint id(_) = 0
|
||||
|
||||
contract C =
|
||||
payable stateful entrypoint
|
||||
call_missing_con() : int = (ct_1111111111111111111111111111112JF6Dz72 : Remote).id(value = 1, 0)
|
||||
|
||||
namespace ShadowingConst =
|
||||
let const = 1
|
||||
|
||||
function f() =
|
||||
let const = 2
|
||||
const
|
||||
|
||||
namespace UnusedConstNamespace =
|
||||
// No warnings should be shown even though const is not used
|
||||
let const = 1
|
||||
|
||||
contract UnusedConstContract =
|
||||
// Only `c` should show a warning because it is never used in the contract
|
||||
let a = 1
|
||||
let b = 2
|
||||
let c = 3
|
||||
|
||||
entrypoint f() =
|
||||
// Both normal access and qualified access should prevent the unused const warning
|
||||
a + UnusedConstContract.b
|
||||
|
||||
Reference in New Issue
Block a user