tracing aiken build: proofread
This commit is contained in:
parent
e2db317b30
commit
b340cfd2f0
|
@ -8,7 +8,7 @@ Aims:
|
||||||
|
|
||||||
The motivation for writing this came from a desire to add additional features to Aiken not yet available.
|
The motivation for writing this came from a desire to add additional features to Aiken not yet available.
|
||||||
One such feature would evaluate an arbitrary function in Aiken callable from JavaScript.
|
One such feature would evaluate an arbitrary function in Aiken callable from JavaScript.
|
||||||
This would help a lot with testing trying to align on and off-chain code.
|
This would help a lot with testing and when trying to align on and off-chain code.
|
||||||
|
|
||||||
Another more pipe dreamy, ad-hoc function extraction - from a span of code, generate a function.
|
Another more pipe dreamy, ad-hoc function extraction - from a span of code, generate a function.
|
||||||
A digression to answer _why would this be at all helpful?!_
|
A digression to answer _why would this be at all helpful?!_
|
||||||
|
@ -23,9 +23,9 @@ Possible solutions:
|
||||||
The problems are:
|
The problems are:
|
||||||
|
|
||||||
1. Requires relentless constructing and deconstructing across the function call.
|
1. Requires relentless constructing and deconstructing across the function call.
|
||||||
And this is adds costs in Aiken.
|
This adds costs.
|
||||||
2. Becomes tedious aligning the definition and function call.
|
2. Becomes tedious aligning the definition and function call.
|
||||||
3. End up with very long validators which are hard to unit test.
|
3. Ends up with very long validators which are hard to unit test.
|
||||||
|
|
||||||
My current preferred way is to accept that validator functions are long.
|
My current preferred way is to accept that validator functions are long.
|
||||||
Ad-hoc function extraction would allow for sections of code to be tested without needing to be factored out.
|
Ad-hoc function extraction would allow for sections of code to be tested without needing to be factored out.
|
||||||
|
@ -35,18 +35,17 @@ To do either of these, we need to get to grips with the Aiken compilation pipeli
|
||||||
### This won't age well
|
### This won't age well
|
||||||
|
|
||||||
Aiken is undergoing active development.
|
Aiken is undergoing active development.
|
||||||
This post was started life with Aiken ~v1.14.
|
This post started life with Aiken ~v1.14.
|
||||||
With Aiken v1.15, there were already reasonably significant changes to the compilation pipeline.
|
Aiken v1.15 introduced reasonably significant changes to the compilation pipeline.
|
||||||
The word is that there aren't as big changes in the near future,
|
The word is that there aren't any more big changes in the near future,
|
||||||
but this article will undoubtedly begin to diverge from the current code-base even before publishing.
|
but this article will undoubtedly begin to diverge from the current code-base even before publishing.
|
||||||
|
|
||||||
### Limitations of narrating code
|
### Limitations of narrating code
|
||||||
|
|
||||||
Narrating code becomes a compromise between being honest and accurate, and being readable and digestible.
|
Narrating code becomes a compromise between being honest and accurate, and being readable and digestible.
|
||||||
Following the command `aiken build` covers well in excess of 10,000 LoC.
|
The command `aiken build` covers well in excess of 10,000 LoC.
|
||||||
The writing of this post ground slowly to a halt as it progressed deeper into the code
|
The writing of this post ground to a halt as it reached deeper into the code-base.
|
||||||
with the details seeming to increase in importance.
|
To redeem it, some (possibly large) sections remain black boxes.
|
||||||
At some point I had to draw a line and resign to fact that some parts will remain black boxes for now.
|
|
||||||
|
|
||||||
## Aiken build
|
## Aiken build
|
||||||
|
|
||||||
|
@ -67,7 +66,7 @@ At a high level we are trying to do something straightforward: reformulate Aiken
|
||||||
Some Aiken expressions are relatively easy to handle for example an Aiken `Int` goes to an `Int` in Uplc.
|
Some Aiken expressions are relatively easy to handle for example an Aiken `Int` goes to an `Int` in Uplc.
|
||||||
Some Aiken expressions require more involved handling, for example an Aiken `If... If Else... Else `
|
Some Aiken expressions require more involved handling, for example an Aiken `If... If Else... Else `
|
||||||
must have the branches "nested" in Uplc.
|
must have the branches "nested" in Uplc.
|
||||||
Aiken also have lots of nice-to-haves like pattern matching, modules, and generics.
|
Aiken has lots of nice-to-haves like pattern matching, modules, and generics;
|
||||||
Uplc has none of these.
|
Uplc has none of these.
|
||||||
|
|
||||||
### The Preamble
|
### The Preamble
|
||||||
|
@ -114,9 +113,9 @@ Things become a bit intimidating at this point in terms of sheer lines of code:
|
||||||
`gen_uplc.rs` and three modules in `gen_uplc/` totals > 8500 LoC.
|
`gen_uplc.rs` and three modules in `gen_uplc/` totals > 8500 LoC.
|
||||||
|
|
||||||
Aiken has its own _intermediate representation_ called `air` (as in Aiken Intermediate Representation).
|
Aiken has its own _intermediate representation_ called `air` (as in Aiken Intermediate Representation).
|
||||||
These are common in compiled languages.
|
Intermediate representations are common in compiled languages.
|
||||||
`Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
`Air` is defined in `aiken-lang/src/gen_uplc/air.rs`.
|
||||||
Unsurprisingly, it looks little bit like a language between Aiken and plutus.
|
Unsurprisingly, it looks a little bit like a language between Aiken and plutus.
|
||||||
|
|
||||||
In fact, Aiken has another intermediate representation: `AirTree`.
|
In fact, Aiken has another intermediate representation: `AirTree`.
|
||||||
This is constructed between the `TypedExpr` and `Vec<Air>` ie between parsed Aiken and air.
|
This is constructed between the `TypedExpr` and `Vec<Air>` ie between parsed Aiken and air.
|
||||||
|
@ -134,12 +133,12 @@ It does so by
|
||||||
self.assignment >> self.expect_type_assign >> self.code_gen_functions.insert
|
self.assignment >> self.expect_type_assign >> self.code_gen_functions.insert
|
||||||
```
|
```
|
||||||
and thus is creating a hashmap of all the functions that appear in the definition.
|
and thus is creating a hashmap of all the functions that appear in the definition.
|
||||||
From the call to return of `assign` covers > 600 LoC so we'll leave this as otherwise a black box.
|
From the call to return of `assign` covers > 600 LoC so we'll leave this as a black box.
|
||||||
(`self.handle_each_clause` is also called with `mut` which in turn calls `self.build` for which `mut` it is needed.)
|
(`self.handle_each_clause` is also called with `mut` which in turn calls `self.build` for which `mut` it is needed.)
|
||||||
|
|
||||||
Validators in Aiken are boolean functions while in Uplc they are unit-valued (aka void-valued) functions.
|
Validators in Aiken are boolean functions while in Uplc they are unit-valued (aka void-valued) functions.
|
||||||
Thus the air tree is wrapped such that `false` results in an error (`wrap_validator_condition`).
|
Thus the air tree is wrapped such that `false` results in an error (`wrap_validator_condition`).
|
||||||
I don't know why there is a prevailing thought that boolean functions are preferable than functions
|
I don't know why there is a prevailing thought that boolean functions are preferable to functions
|
||||||
that error if anything is wrong - which is what validators are.
|
that error if anything is wrong - which is what validators are.
|
||||||
|
|
||||||
`check_validator_args` again extends the airtree from the previous step,
|
`check_validator_args` again extends the airtree from the previous step,
|
||||||
|
@ -186,7 +185,7 @@ As `AirTree` is for internal use only, the scope for potential problems is reaso
|
||||||
It seems likely this is to avoid similar-yet-different IRs between steps.
|
It seems likely this is to avoid similar-yet-different IRs between steps.
|
||||||
However, the trade off is that it partially obfuscates what is a valid state where.
|
However, the trade off is that it partially obfuscates what is a valid state where.
|
||||||
|
|
||||||
What is hoisting? hoisting gives the airtree depth.
|
What is hoisting? Hoisting gives the airtree depth.
|
||||||
The motivation is that by the time we hit Uplc it is "generally better"
|
The motivation is that by the time we hit Uplc it is "generally better"
|
||||||
that
|
that
|
||||||
|
|
||||||
|
@ -194,7 +193,7 @@ that
|
||||||
- the definition appears as close to use as possible
|
- the definition appears as close to use as possible
|
||||||
|
|
||||||
Hoisting creates tree paths.
|
Hoisting creates tree paths.
|
||||||
The final airtree to airtree step is`self.hoist_functions_to_validator` traverses the paths.
|
The final airtree to airtree step, `self.hoist_functions_to_validator`, traverses these paths.
|
||||||
There is a lot of mutating of self, making it quite hard to keep a handle on things.
|
There is a lot of mutating of self, making it quite hard to keep a handle on things.
|
||||||
In all this (several thousand?) LoC, it is essentially ascertaining in which node of the tree
|
In all this (several thousand?) LoC, it is essentially ascertaining in which node of the tree
|
||||||
to insert each function definition.
|
to insert each function definition.
|
||||||
|
@ -220,7 +219,7 @@ It flattens the tree to a vec.
|
||||||
Next we go from `Vec<Air> -> Term<Name>`.
|
Next we go from `Vec<Air> -> Term<Name>`.
|
||||||
This step is a little more involved than the previous.
|
This step is a little more involved than the previous.
|
||||||
For one, this is executed in the context of the code generator.
|
For one, this is executed in the context of the code generator.
|
||||||
Moreover, the code generator is treated mutable - ouch.
|
Moreover, the code generator is treated as mutable - ouch.
|
||||||
|
|
||||||
On further inspection we see that the only mutation is setting `self.needs_field_access = true`.
|
On further inspection we see that the only mutation is setting `self.needs_field_access = true`.
|
||||||
This flag informs the compiler that, if true, additional terms must be added in one of the final steps
|
This flag informs the compiler that, if true, additional terms must be added in one of the final steps
|
||||||
|
@ -232,7 +231,7 @@ Some examples:
|
||||||
|
|
||||||
- `Air::Var` require 100 LoC to do case handling on different constructors.
|
- `Air::Var` require 100 LoC to do case handling on different constructors.
|
||||||
- Lists in air have no immediate analogue in uplc
|
- Lists in air have no immediate analogue in uplc
|
||||||
- builtins, as in built-in functions (standard shorthand), have to mediated
|
- builtins, as in built-in functions (standard shorthand), have to be mediated
|
||||||
with some combination of `force` and `delay` in order to behave as they should.
|
with some combination of `force` and `delay` in order to behave as they should.
|
||||||
- user functions must be "uncurried", ie treated as a sequence of single argument functions,
|
- user functions must be "uncurried", ie treated as a sequence of single argument functions,
|
||||||
and recursion must be handled
|
and recursion must be handled
|
||||||
|
@ -240,9 +239,8 @@ and recursion must be handled
|
||||||
|
|
||||||
#### Cranking the Optimizer
|
#### Cranking the Optimizer
|
||||||
|
|
||||||
There is a sequence of operations performed on the Uplc mapping `Term<Name> -> Term<Name>`.
|
There is a sequence of operations performed on the Uplc, mapping `Term<Name> -> Term<Name>`.
|
||||||
These remove inconsequential parts of the logic which will appear.
|
This removes inconsequential parts of the logic which have been generated, including:
|
||||||
These include:
|
|
||||||
|
|
||||||
- removing application of the identity function
|
- removing application of the identity function
|
||||||
- directly substituting where apply lambda is applied to a constant or builtin
|
- directly substituting where apply lambda is applied to a constant or builtin
|
||||||
|
@ -259,7 +257,7 @@ The generated program can now be serialized and included in the blueprint.
|
||||||
### Plutus Core Signposting
|
### Plutus Core Signposting
|
||||||
|
|
||||||
All this fuss is to get us to a point where we can write Uplc - and good Uplc at that.
|
All this fuss is to get us to a point where we can write Uplc - and good Uplc at that.
|
||||||
Note that there's many ways to generate code and most of them are bad.
|
Note that there are many ways to generate code and most of them are bad.
|
||||||
The various design decisions and compilation steps make more sense
|
The various design decisions and compilation steps make more sense
|
||||||
when we have a better understanding of the target language.
|
when we have a better understanding of the target language.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue