Expand description
The intermediate representation code generation reads.
The typer produces it. It is the only phase that knows a node’s type, and every node
here carries one (DEC-18 decision
1).
Term is the untyped half — what the translation from the canonical AST builds —
and TypedTerm is the same tree once inference has solved a type for each of its
nodes. A Module holds the unions a module declares and one Declaration per
value: everything about a module that only emission asks for.
It is not the whole of what a backend is handed. check_module answers with a
CheckedModule, which is this beside the
canonical::Module it was built from, and two of the things emission needs are
still only on that half — a module’s exports, which is what a JavaScript module has
to export, and canonical::Value::TypedValue’s marked_unsafe, which
zelkova_js::emit reads because an unsafe
signature and an effectful one emit differently. Nothing here duplicates them.
The type language itself is still typer::Type. It is
the typer’s own representation and unification is written against it, so it stays
there; this module names it and adds nothing to it.
§What this shape owes WebAssembly
One IR serves both targets and JavaScript is written first (DEC-18 decision
2),
so a reader arriving while only the JavaScript backend exists will find things
JavaScript has no use for. None of them is spare:
- A type on every node, and what a constrained one asks of it. JavaScript needs
almost none of them: the canonical AST already separates an
Intliteral from aFloatone, and arithmetic and equality are ordinary functions behind facades. WebAssembly is statically typed, and a node’s representation class — ani64, anf64, a reference — is read off its type. Solved types are also what monomorphisation consumes, which is the only way polymorphism reaches a target where a class dictionary is erased by specialisation and never passed. A specialiser needs the other half of a constrained name too, and there are three places it is written. A reference to a name whose type has a context carries that context as instantiated at the use, onePredicateper constraint, with the final substitution applied like every other type on the node:eqused atIntcarriesEq Int. ADeclarationcarries its own context, read off its solved type the same way. And aModulecarries itsInstances, each with its class, head, context and one checked body per member. A hole, a name that did not resolve, has a type too: the one inference solved for its position, which is an unsolved variable when nothing around it constrains it. - A constructor’s index within its declaration, and not only its name. A union is
a WIT
variantwith one case per constructor and a tuple is atuple(A union crosses as a tagged value), so a constructor is reached by its position there. JavaScript writes the name into the$field and never asks for the index. - Arity as a fact and saturation per call site. A declaration emits as a plain
n-ary function on both targets (
DEC-18decision 3), and a facade’s plain parameter list is the same call shape either way. WebAssembly has no closure primitive, so how a partial application is represented there is open — and an IR that made a call site’s saturation something to re-derive would make that question harder rather than leaving it open. - A record’s fields by label, and the order a positional target reads them in. A
record crosses as a WIT
record, which reaches a field by position (Which types may cross the boundary), and a record type is a set of fields with no order of its own (a record type is a set of fields), so nothing in the IR numbers them:TypedTermKind::Access,TypedTermKind::Accessor,TypedTermKind::Updateand a record pattern’s entries (Step::Field) name a field by its label, and JavaScript reads it as the property of that name. A positional target reads the order off the type, which is on the access node already — theType::Recordof therecordoperand, of the accessor’s parameter, of the update’s own type, of the value a pattern is matched against. That order is label order: the labels sorted by their characters, each compared by its code point, a label that another begins with first. It is the order aType::Record’s map iterates in, and the one two spellings of one type agree on —{ a : Int, b : Int }and{ b : Int, a : Int }are one type and have one layout — which is why Records and derivation walks a record’s fields in it too: sorting is the only order available that does. The order a record expression’s fields were written in is not a position. It is on the term (TypedTermKind::Record,TypedTermKind::Update) as an order of evaluation, and a target evaluates in it and lays the value out in label order.
GEN-15 holds the questions a WebAssembly backend
still has to answer — linear memory or WasmGC, how a partial application is
represented, whether monomorphisation is whole-program — and is unscheduled. Nothing
in this module is a JavaScript decision, and a change that makes one of the facts
above unavailable is a change that closes that ticket’s options.
§What specialisation adds
build knows one module and leaves each reference that carried obligations as the typer
solved it. specialise reads every module of a build, resolves each of them to the
instance’s member or to a Specialisation of the module that uses it, and changes the
modules in place: a ReferenceKind::InstanceMember or a ReferenceKind::Specialised
replaces the name and its obligations, and Module::specialisations holds the copies. A
constrained Declaration and the members of an instance that has a context stay as they
were written: they are what is copied, and nothing emits them as they stand.
initialisation_items is the order of the parameterless items once those are among
them. The module doc comment of specialise is the account, and
GEN-15 needs the same pass for a reason of its own.
§What is not here yet
One ticket adds to this shape and is deliberately not written into it yet: a self tail
call (GEN-6). Module holds its
declarations in a Vec sorted by name, which is a deterministic order and not an
evaluation order; Module::initialisation_order is the evaluation order, over the
parameterless declarations alone, and initialisation_items extends it to the items
specialisation adds; zelkova_js::emit emits them in it.
A case’s branches keep TypedTermKind::Case’s own flat shape rather than
growing a tree in place: the typer still wants a flat list, since every branch is
checked against the same scrutinee type and order does not matter to it. The
decision module is a second, additional view of the same branches — built from
them on demand rather than stored alongside them — and Decision is the tree a
backend walks instead of re-deriving, at emission time, which test distinguishes
which branch (GEN-5). See that module’s doc comment.
Structs§
- Binding
- One name a
Decision::Leafbinds: the value atoccurrence, of typetpe. - Body
- A declaration’s parameters and the expression they are in scope over.
- Constructor
- A constructor, and its place in the declaration that declares it.
- Declaration
- One value of a module: what a backend emits as a named binding.
- Field
- One field of a record or an update —
TermKind::Record,TermKind::Updateand their typed counterparts — over the term typeTof the tree it is in; and, as aField<SubPattern>, one entry of a record pattern. - Instance
- One
instancedeclaration of a module: which class, which type, what it needs of that type’s arguments, and a checked body for each member. - Instance
Member - A member of an instance that has no context, which is an ordinary declaration of the module that declares the instance, emitted once.
- Module
- One checked module’s emittable shape: what a backend reads that no earlier phase carried.
- Module
Errors - The errors one module of the build has, by the module the code that raised them was written in: the module whose file the labels point into.
- Predicate
- A class required of a type:
Eq Int,Comparable a. - Reference
- A reference to a name, and what kind of name it is.
- Solved
Instance - What the typer has to say about one
instancedeclaration: the type it is for, what it needs, and an answer for each binding of its body. - Specialisation
- A constrained declaration, or a member of an instance with a context, at one assignment of ground types to its constrained variables: an ordinary declaration of the module that uses it.
- SubPattern
- A pattern written in a position inside another one — a constructor’s argument, a tuple’s element or a record pattern’s entry — and the type of the value found there.
- Term
- An untyped term, and where the expression it was translated from was written.
- Term
Pattern - Simplified pattern used inside a
Term, and where it was written. - Type
Binder - Bind a name and a type together. Used in function and let expression
- Typed
Term - Like a Term but with an associated Type, and still with its position. Any term introducing a name will have a TypeBinder instead.
- Unchecked
- A declaration the typer did not type, and which therefore has no IR.
- Union
- A union declaration: the type a constructor builds, and the constructors that build it.
- Variant
- One constructor of a
Union.
Enums§
- Case
Form - What the source wrote that a
Caseterm was built from. - Decision
- The tree a
case’s branches lower to: what test distinguishes which branch, in what order they are tried, and the names each leaf binds. - Item
- One thing a module emits under a name of its own: where it is, in the module.
- Literal
Value - The concrete value a
TermPatternKind::Literalpattern tests for. - Occurrence
- A path to one value reached from a
case’s scrutinee: the scrutinee itself, or one step inside another occurrence. A pattern nestedndeep is reached by a path ofnSteps. - Outcome
- The value a
Decision::Testchecks for. - Reference
Kind - Which of the things a
Referencenames: a name bound in the declaration, a declaration of this module, one of another, or a constructor — and, oncespecialisehas run, a specialisation or an instance’s member, which are what a name that asked for an instance becomes. - Saturation
- Whether an application supplies every argument its callee takes.
- Solved
- What the typer has to say about one declaration.
- Specialise
Error - Why the specialisations of a build could not be found.
- Step
- One step of an
Occurrence: which sub-value of a constructor, a tuple or a record. A constructor’s argument and a tuple’s element are counted from zero the wayConstructor::indexcounts a case; a record’s field is named by its label. - Subject
- What a
Specialisationis a copy of. - Term
Kind - Term
Pattern Kind - Typed
Term Kind
Constants§
- SPECIALISATION_
LIMIT - How many times one declaration may appear in one chain of specialisations before the chain is reported as having no end.
Functions§
- build
- Turn a checked module and what the typer solved for it into the IR a backend reads.
- decision_
tree - Lower one
case’s branches, in source order, into theDecisiona backend walks. - initialisation_
items - The items of
modulethat take no parameter, in the order they must be initialised: each only after every parameterless item it depends on, directly or through a function it mentions. - mentioned_
by_ copies - The values of
modulethat a copy of its code, placed in another module, names: the declarations its constrained declarations and the bindings of its instances mention. - specialise
- Find every specialisation
modulesneed and resolve every reference that carried obligations, in place.