// The type section: which types are emitted, and at which indices.
//
// Ported from the type-field conversion of wax/src/lib-conversion/to_wasm.ml
// together with the index assignment of wax/src/lib-wasm/text_to_binary.ml.
//
// The store is NOT the section. The store interns -- two declarations of the
// same shape share one entry, which is the canonicalisation wasm's own type
// equivalence performs, and the checker needs it to answer subtyping. The
// section does not intern: `type sig = fn();` and `type elems = fn();` are two
// entries at two indices, and `&?elems` means the second one.
//
// So the section is built from the SOURCE's declarations -- one entry per name,
// in the order they were written -- and the store is consulted only for what
// each name means. Types the store holds that no declaration names are the
// synthesized ones, and they follow.
//
// The store index and the emitted index are therefore different numbers, and
// every reference has to be translated between them. The translation is
// installed per module in a mutable cell rather than threaded, exactly as the
// reference does and for the same reason: it is read from the leaf functions
// that turn an annotation into a value type, and threading it there would put a
// parameter on every one of their callers to no purpose.
///|
/// Store index to emitted index, for the module being lowered.
///
/// Empty means the identity, which is what the unit tests want: they build a
/// module without a source to lay out.
let type_remap : Ref[Map[Int32, Int32]] = { val: Map([]), }
///|
/// The emitted index a store index became.
fn emitted_type_index(store_index : Int32) -> Int32 {
// Several declarations of one shape under different conditions: the one
// visible from where the reference stands.
if current_layout.val is Some(layout) &&
layout.visible_slot(store_index) is Some(i) {
return i
}
match type_remap.val.get(store_index) {
Some(i) => i
None => store_index
}
}
///|
/// Emitted index back to store index, the inverse of `type_remap`.
let store_remap : Ref[Array[Int32]] = { val: [], }
///|
/// The store index behind an emitted one.
fn store_type_index(emitted : Int32) -> Int32 {
let m = store_remap.val
if emitted >= 0 && emitted < m.length() {
m[emitted]
} else {
emitted
}
}
///|
/// The layout of the module being lowered, for the leaf functions that resolve
/// a reference by name. Companion to `type_remap`, and installed with it.
let current_layout : Ref[TypeLayout?] = { val: None, }
///|
/// The emitted index a type NAME means.
///
/// A declared name has its own entry. A SYNTHESIZED one -- ``, the
/// type a `ref.func` gives its function -- has none until something refers to
/// it, and then it gets one of its own rather than borrowing the like-shaped
/// declaration it interned with: the reference materialises it too, and the
/// index is observable.
fn emitted_named_index(name : String, store_index : Int32) -> Int32 {
guard current_layout.val is Some(layout) else {
return emitted_type_index(store_index)
}
let at = named_index(layout, name, store_index)
layout.referenced[at] = true
// Its OWN entry, not a declared type it was found equal to.
if is_synthetic(name) &&
layout.by_name.get(name) == Some(at) &&
!layout.declared_slots.contains(at) &&
!layout.extras_seen.contains(name) {
layout.extras_seen.push(name)
}
at
}
///|
fn named_index(
layout : TypeLayout,
name : String,
store_index : Int32,
) -> Int32 {
if layout.resolve_name(name, layout.site) is Some(i) {
return i
}
if layout.aliases.get(name) is Some(i) {
return i
}
if !is_synthetic(name) {
return emitted_type_index(store_index)
}
// A synthesized type REUSES a declared one of the same definition rather
// than being materialised beside it. The comparison is on the whole written
// subtype -- finality and supertype included -- not on the interned entry:
// interning normalises exactly the differences that decide this, so two
// names can share a store entry and still be two types here.
// An entry no DECLARATION put there is simply this type's own: interning
// did not merge it with anything that has a definition of its own, so there
// is nothing for it to be confused with.
if layout.by_store.get(store_index) is Some(existing) {
// An entry placed FOR this name is this name's, and carries it: the
// reference materialises a defined function's signature under its own
// name once something refers to it. An entry this name merely reaches --
// ``, or an import's inline signature -- is an alias, and stays
// unnamed.
if layout.slot_names.get(existing) is Some(owner) && owner == name {
layout.by_name[name] = existing
return existing
}
}
guard layout.written.get(name) is Some(mine) else {
return emitted_type_index(store_index)
}
for entry in layout.declared {
if same_written(entry.0, mine) && layout.slot_visible(entry.1) {
// Remembered only when no conditional makes the answer depend on where
// the reference stands.
if layout.conds.is_empty() {
layout.by_name[name] = entry.1
}
return entry.1
}
}
let emitted = layout.types.length()
layout.by_name[name] = emitted
layout.store_of.push(store_index)
layout.types.push(layout.pending[store_index])
layout.groups.push({ start: emitted, len: 1, explicit: false, })
emitted
}
///|
/// The emitted indices that share a rec group with another type.
fn in_shared_groups(layout : TypeLayout) -> Map[Int32, Unit] {
let out : Map[Int32, Unit] = Map([])
for g in layout.groups {
if g.len > 1 {
for k in g.start..<(g.start + g.len) {
out[k] = ()
}
}
}
out
}
///|
/// The emitted index of a store entry no NAME refers to here, materialising an
/// unnamed one if the section holds none.
///
/// A string literal's byte array is such a reference: the reference writes the
/// literal as an `@string` annotation, which takes the first array type of
/// that shape the section holds -- one the source declared, or one a name
/// materialised -- and otherwise appends an unnamed one where the literal is.
/// Referring to it by its synthesized name instead would make it a field.
fn emitted_store_index(store_index : Int32) -> Int32 {
guard current_layout.val is Some(layout) else {
return emitted_type_index(store_index)
}
match layout.by_store.get(store_index) {
Some(e) => layout.visible_slot(store_index).unwrap_or(e)
None => {
let emitted = layout.types.length()
layout.by_store[store_index] = emitted
layout.store_of.push(store_index)
layout.types.push(layout.pending[store_index])
layout.groups.push({ start: emitted, len: 1, explicit: false, })
emitted
}
}
}
///|
/// The emitted index of a function SHAPE, materialising one if the section
/// holds none.
///
/// A block written with parameters -- `do (i32, i32) -> i32 { .. }` -- names
/// its signature by shape rather than by name, and nothing may have interned
/// that shape: it is not a declaration, and no function has it. The entry it
/// then needs is a real type in the section, appended like any other.
fn emitted_shape_index(
params : Array[@wasm_types.ValType[Int32]],
results : Array[@wasm_types.ValType[Int32]],
) -> Int32? {
guard current_layout.val is Some(layout) else { return None }
let grouped = in_shared_groups(layout)
for emitted, sub in layout.types {
if sub.composite is Func(ft) &&
sub.final_ &&
sub.supertypes.is_empty() &&
!grouped.contains(emitted) &&
ft.params == params &&
ft.results == results {
return Some(emitted)
}
}
let emitted = layout.types.length()
layout.store_of.push(-1)
layout.types.push({
final_: true,
supertypes: [],
descriptor: None,
describes: None,
composite: Func({ params, results, }),
})
layout.groups.push({ start: emitted, len: 1, explicit: false, })
Some(emitted)
}
///|
/// Where each type name lands in the emitted section.
priv struct TypeLayout {
/// Emitted index for a declared name: its FIRST declaration, when branches
/// of a conditional declare it more than once (see `decls`).
by_name : Map[String, Int32]
/// Every declaration of a name, with where it was written and the emitted
/// index it took. Two branches of a conditional may each declare a name, and
/// the text form writes both, each at an index of its own.
decls : Map[String, Array[(Int32, @basic.Location)]]
/// What each declaration's slot holds, as its own branch defined it.
decl_written : Map[Int32, @ast.SubType]
/// The conditional groups the source wrote, for the site a reference stands
/// at to pick the declaration it can see.
conds : Array[@wasm_bin.CondGroup]
/// Where the reference being resolved stands: the field being lowered.
mut site : @basic.Location?
/// Emitted index for a store index: the FIRST declaration of that shape,
/// which is the one a reference carrying only an interned id must mean.
by_store : Map[Int32, Int32]
/// The store index behind each emitted one, for the reads that go back to
/// the store for a definition.
store_of : Array[Int32]
types : Array[@wasm_bin.SubType]
groups : Array[@wasm_bin.RecGroup]
/// Every store entry already lowered, so a synthesized type materialised
/// mid-lowering costs a lookup rather than a re-resolution.
pending : Array[@wasm_bin.SubType]
/// The subtype each name was WRITTEN as, before interning normalised it.
written : Map[String, @ast.SubType]
/// The declared subtypes, with the index each landed at, for the reuse test.
declared : Array[(@ast.SubType, Int32)]
/// The name a store-tier entry was placed FOR, when it was placed for one.
slot_names : Map[Int32, String]
/// The synthesized names that came to own an entry of their own, in the
/// order they were first referred to: the reference's extra types, which
/// it writes after the declared ones (see `type_layout`'s `extras`).
extras_seen : Array[String]
/// Names that resolve to an entry they do not own: the entry was interned
/// for its own reasons and this name merely reaches it. Kept apart from
/// `by_name` because the name section emits the latter and not this.
aliases : Map[String, Int32]
/// Which emitted entries a DECLARATION put there. A synthesized type sharing
/// one of those is a different type that merely interned with it; sharing an
/// entry nothing declared is just itself.
declared_slots : Map[Int32, Bool]
/// The entries something referred to BY NAME while lowering.
///
/// A type materialised for a name is written as a field only when something
/// names it: `(ref $)` in a global's type makes the entry a field,
/// and an entry nothing reaches is spelled inline wherever it is used.
referenced : Map[Int32, Bool]
}
///|
/// Lay out the type section from the source's declarations, then the rest.
fn type_layout(
ctx : @typing_env.ModuleContext,
source : @ast.Module[@basic.Location],
store : @type_store.TypeStore,
extras~ : Array[String] = [],
conds~ : Array[@wasm_bin.CondGroup] = [],
) -> TypeLayout {
let by_name : Map[String, Int32] = Map([])
let decls : Map[String, Array[(Int32, @basic.Location)]] = Map([])
let decl_written : Map[Int32, @ast.SubType] = Map([])
let by_store : Map[Int32, Int32] = Map([])
let store_of : Array[Int32] = []
let groups : Array[@wasm_bin.RecGroup] = []
fn place(name : @ast.Ident, store_index : Int32, def : @ast.SubType) -> Unit {
let emitted = store_of.length()
if !by_name.contains(name.name) {
by_name[name.name] = emitted
}
match decls.get(name.name) {
Some(l) => l.push((emitted, name.loc))
None => decls[name.name] = [(emitted, name.loc)]
}
decl_written[emitted] = def
if !by_store.contains(store_index) {
by_store[store_index] = emitted
}
store_of.push(store_index)
}
// The declarations, in source order, one rec group per `type` field.
@typing.walk_fields(ctx, source, field => {
guard field.desc is Type(rectype) else { return }
let start = store_of.length()
for entry in rectype {
let (name, _) = entry.desc
// Under the branch's own assumption (`walk_fields` sets it), so a name
// two branches declare reads as this branch's declaration.
guard ctx.type_context.types.find_no_mark(name.name) is Some((idx, def)) else {
continue
}
guard idx is Def(id) else { continue }
place(name, id.to_int_for_tests_only(), def)
}
// Pushed even when EMPTY: `rec {}` is a rec group of no types, and it is
// WRITTEN -- a group the author declared and the format can spell, not an
// absence.
let len = store_of.length() - start
groups.push({ start, len, explicit: len != 1, })
})
// Then the synthesized types that own an entry of their own -- the
// reference's extra types, which it writes after every declared one and
// before anything it interns from a signature, in its hash table's order
// (`extras`, worked out by a first lowering). An inline signature of the
// same shape then reuses one of them, as the reference's does. Kept out of
// `by_name` until the declared types have been read off it below.
let extra_slots : Array[(String, Int32)] = []
for name in extras {
guard ctx.type_context.types.find_no_mark(name) is Some((Def(id), _)) else {
continue
}
let si = id.to_int_for_tests_only()
let emitted = store_of.length()
if !by_store.contains(si) {
by_store[si] = emitted
}
store_of.push(si)
groups.push({ start: emitted, len: 1, explicit: false, })
extra_slots.push((name, emitted))
}
// Then the types the LOWERING interns rather than the checker, in the order
// the sections that need them are written -- which is binary section order,
// not source order: imports, then functions, then tags. A module whose tags
// are declared first still emits its function types first, because the
// function section is written first.
//
// These are placed by STORE ENTRY, not by name: a signature with no name of
// its own is interned structurally, so two functions of one shape share an
// index rather than taking two.
fn place_store(store_index : Int32) -> Unit {
if by_store.contains(store_index) {
return
}
let start = store_of.length()
by_store[store_index] = start
store_of.push(store_index)
groups.push({ start, len: 1, explicit: false, })
}
// The name a store-tier entry was placed FOR. Its written form is what gets
// lowered: a signature's inner references mean the names the signature
// wrote, and interning has already merged those names with any like-shaped
// siblings -- so reading the entry back from the store would resolve them to
// whichever sibling happened to be declared first.
let slot_names : Map[Int32, String] = Map([])
// What each of those was written as, read under the assumption it was
// placed under: two branches may mint one synthesized name for two
// signatures -- an import in one, a definition in the other.
let slot_written : Map[Int32, @ast.SubType] = Map([])
fn place_named(name : String) -> Unit {
guard ctx.type_context.types.find_no_mark(name) is Some((Def(id), def)) else {
return
}
let before = store_of.length()
place_store(id.to_int_for_tests_only())
if store_of.length() > before {
slot_names[before] = name
slot_written[before] = def
}
}
fn place_import_signature(decl : @ast.ImportDecl) -> Unit {
match decl.kind {
Func(..) => place_named("")
Tag(..) => place_named("")
_ => ()
}
}
@typing.walk_fields(ctx, source, field => {
match field.desc {
// An IMPORT's signature is a module-level reference and is interned when
// the import is registered, so it comes before anything a body needs --
// and a tag import's signature comes with it, at its written position,
// rather than with the defined tags much later.
Import(decl~, ..) => place_import_signature(decl.desc)
ImportGroup(decls~, ..) =>
for d in decls {
place_import_signature(d.desc)
}
_ => ()
}
})
@typing.walk_fields(ctx, source, field => {
match field.desc {
Func(name~, ..) => place_named("")
_ => ()
}
})
// A defined tag's inline signature is NOT placed here: the reference interns
// it after every body's block types, so the lowering resolves it once the
// code is lowered. An entry only a tag uses is left for then.
let tag_only : Map[Int32, Unit] = Map([])
@typing.walk_fields(ctx, source, field => {
match field.desc {
Tag(name~, typ=None, ..) =>
match
ctx.type_context.types.find_no_mark("") {
Some((Def(id), _)) if !by_store.contains(id.to_int_for_tests_only()) =>
tag_only[id.to_int_for_tests_only()] = ()
_ => ()
}
_ => ()
}
})
// Then everything the store holds that nothing above reached, in the order
// it was interned.
let mut store_index = 0
for group in store.get_all_rectypes() {
let start = store_of.length()
let mut added = 0
for _ in group {
if !by_store.contains(store_index) && !tag_only.contains(store_index) {
by_store[store_index] = store_of.length()
store_of.push(store_index)
added = added + 1
}
store_index = store_index + 1
}
if added > 0 {
groups.push({ start, len: added, explicit: added != 1, })
}
}
type_remap.val = by_store
store_remap.val = store_of
let info = store.subtyping_info()
let types : Array[@wasm_bin.SubType] = []
let pending : Array[@wasm_bin.SubType] = []
let written : Map[String, @ast.SubType] = Map([])
let declared : Array[(@ast.SubType, Int32)] = []
let aliases : Map[String, Int32] = Map([])
let declared_slots : Map[Int32, Bool] = Map([])
let referenced : Map[Int32, Bool] = Map([])
for _, i in by_name {
declared_slots[i] = true
}
for i, _ in decl_written {
declared_slots[i] = true
}
// As WRITTEN, aliases included: a signature written with an alias is not
// the one written with what the alias stands for, and a synthesized type
// reuses only a declaration written the same.
for entry in ctx.type_context.types.iter_entries() {
let (name, (_, sub)) = entry
written[name] = ctx.type_context.written_form(name, sub)
}
for name, i in by_name {
if written.get(name) is Some(sub) {
declared.push((sub, i))
}
}
for e in extra_slots {
by_name[e.0] = e.1
slot_names[e.1] = e.0
}
let layout = {
by_name,
decls,
decl_written,
conds,
site: None,
by_store,
store_of,
types,
groups,
pending,
written,
declared,
declared_slots,
referenced,
aliases,
slot_names,
extras_seen: [],
}
current_layout.val = Some(layout)
for si in 0.. Some(sub)
None =>
match slot_written.get(emitted) {
Some(sub) => Some(sub)
None =>
match slot_names.get(emitted) {
Some(name) => layout.written.get(name)
None => None
}
}
}
// A declaration's inner references are read from where it was written:
// under a conditional, a name the branch declares means its own.
layout.site = layout.decl_site(emitted)
match written_form {
Some(sub) => types.push(layout.source_subtype(ctx, sub))
None =>
types.push(
lower_subtype(info.get_subtype(@type_store.Id::of_index(si))),
)
}
}
layout.site = None
layout
}
///|
/// The declared slot of a store entry visible from the current site, when
/// declarations under different conditions share the entry and the first of
/// them is not visible from there. `None` leaves the answer to `type_remap`.
fn TypeLayout::visible_slot(self : TypeLayout, store_index : Int32) -> Int32? {
guard self.site is Some(_) else { return None }
guard self.by_store.get(store_index) is Some(first) else { return None }
if self.slot_visible(first) {
return None
}
for slot, si in self.store_of {
if si == store_index &&
self.decl_written.contains(slot) &&
self.slot_visible(slot) {
return Some(slot)
}
}
None
}
///|
/// Whether the declaration at a slot can be seen from the current site: its
/// conditional guards are a prefix of the site's. A slot no declaration put
/// there, or a reference with no site, sees everything.
fn TypeLayout::slot_visible(self : TypeLayout, slot : Int32) -> Bool {
guard self.site is Some(site) else { return true }
match self.decl_site(slot) {
None => true
Some(d) => {
let g = guards_at(self.conds, d)
let here = guards_at(self.conds, site)
g.length() <= here.length() &&
g.iter().zip(here.iter()).all(p => p.0 == p.1)
}
}
}
///|
/// Where the declaration at an emitted index was written, if one put it there.
fn TypeLayout::decl_site(
self : TypeLayout,
emitted : Int32,
) -> @basic.Location? {
for _, l in self.decls {
for e in l {
if e.0 == emitted {
return Some(e.1)
}
}
}
None
}
///|
/// The emitted index of the declaration of `name` a reference at `at` can see:
/// the one whose conditional guards are the longest prefix of the site's, as
/// for every other index space (see `Space::resolve`). The first declaration
/// when there is no site, or only one declaration.
fn TypeLayout::resolve_name(
self : TypeLayout,
name : String,
at : @basic.Location?,
) -> Int32? {
guard self.decls.get(name) is Some(l) && l.length() > 1 && at is Some(at) else {
return self.by_name.get(name)
}
let here = guards_at(self.conds, at)
let mut best : Int32? = None
let mut best_len = -1
for entry in l {
let g = guards_at(self.conds, entry.1)
guard g.length() <= here.length() else { continue }
let mut ok = true
for k in 0.. best_len {
best_len = g.length()
best = Some(entry.0)
}
}
match best {
Some(_) => best
None => self.by_name.get(name)
}
}
///|
/// The source declaration at an emitted index, if a declaration put it there.
fn declared_at(
ctx : @typing_env.ModuleContext,
layout : TypeLayout,
emitted : Int32,
) -> @ast.SubType? {
if layout.decl_written.get(emitted) is Some(sub) {
return Some(sub)
}
for name, i in layout.by_name {
if i == emitted {
if ctx.type_context.types.find_no_mark(name) is Some((_, sub)) {
return Some(sub)
}
}
}
None
}
///|
/// A declared type, lowered from what the source wrote.
fn TypeLayout::source_subtype(
self : TypeLayout,
ctx : @typing_env.ModuleContext,
s : @ast.SubType,
) -> @wasm_bin.SubType {
fn idx(n : @ast.Ident) -> Int32 {
match self.resolve_name(n.name, self.site) {
Some(i) => i
None =>
match ctx.type_context.types.find_no_mark(n.name) {
Some((Def(id), _)) => emitted_type_index(id.to_int_for_tests_only())
_ => 0
}
}
}
{
final_: s.final_,
supertypes: match s.supertype {
Some(n) => [idx(n)]
None => []
},
descriptor: s.descriptor.map(n => idx(n)),
describes: s.describes.map(n => idx(n)),
composite: match s.typ {
Func(ft) =>
Func({
params: ft.params.map(p => self.source_valtype(ctx, p.desc.1)),
results: ft.results.map(r => self.source_valtype(ctx, r)),
})
Struct(fields) =>
Struct({
fields: fields.map(f => {
(
{
mut_: f.desc.1.mut_,
typ: self.source_storagetype(ctx, f.desc.1.typ),
} : @wasm_types.MutType[@wasm_types.StorageType[Int32]])
}),
})
Array(f) =>
Array({
element: { mut_: f.mut_, typ: self.source_storagetype(ctx, f.typ), },
})
Cont(n) => Cont(idx(n))
},
}
}
///|
fn TypeLayout::source_storagetype(
self : TypeLayout,
ctx : @typing_env.ModuleContext,
s : @wasm_types.StorageType[@ast.Ident],
) -> @wasm_types.StorageType[Int32] {
match s {
Value(v) => Value(self.source_valtype(ctx, v))
Packed(p) => Packed(p)
}
}
///|
fn TypeLayout::source_valtype(
self : TypeLayout,
ctx : @typing_env.ModuleContext,
v : @wasm_types.ValType[@ast.Ident],
) -> @wasm_types.ValType[Int32] {
match v {
I32 => I32
I64 => I64
F32 => F32
F64 => F64
V128 => V128
// Expanded here: the binary format has no aliases. One that stands for no
// type was reported, and the module is never lowered.
Alias(_) =>
match ctx.type_context.unalias(v) {
Alias(a) => abort("unresolved type alias " + a.name)
t => self.source_valtype(ctx, t)
}
Ref(r) => {
fn idx(n : @ast.Ident) -> Int32 {
match self.resolve_name(n.name, self.site) {
Some(i) => i
None =>
match ctx.type_context.types.find_no_mark(n.name) {
Some((Def(id), _)) =>
emitted_type_index(id.to_int_for_tests_only())
_ => 0
}
}
}
Ref({
nullable: r.nullable,
typ: match r.typ {
Func => Func
NoFunc => NoFunc
Exn => Exn
NoExn => NoExn
Cont => Cont
NoCont => NoCont
Extern => Extern
NoExtern => NoExtern
Any => Any
Eq => Eq
I31 => I31
Struct => Struct
Array => Array
None_ => None_
Type(n) => Type(idx(n))
Exact(n) => Exact(idx(n))
},
})
}
}
}
///|
/// One stored type, with its `Id` indices flattened to plain integers.
fn lower_subtype(s : @type_store.SubType[@type_store.Id]) -> @wasm_bin.SubType {
{
final_: s.final_,
supertypes: match s.supertype {
Some(i) => [emitted_type_index(i.to_int_for_tests_only())]
None => []
},
descriptor: s.descriptor.map(i => {
emitted_type_index(i.to_int_for_tests_only())
}),
describes: s.describes.map(i => {
emitted_type_index(i.to_int_for_tests_only())
}),
composite: lower_comptype(s.typ),
}
}
///|
fn lower_comptype(
c : @type_store.CompType[@type_store.Id],
) -> @wasm_bin.CompositeType {
match c {
Func(ft) =>
Func({
params: ft.params.map(v => lower_valtype(v)),
results: ft.results.map(v => lower_valtype(v)),
})
Struct(fields) =>
Struct({
fields: fields.map(f => {
(
{ mut_: f.mut_, typ: lower_storagetype(f.typ), } :
@wasm_types.MutType[@wasm_types.StorageType[Int32]])
}),
})
Array(f) =>
Array({ element: { mut_: f.mut_, typ: lower_storagetype(f.typ), }, })
Cont(i) => Cont(emitted_type_index(i.to_int_for_tests_only()))
}
}
///|
/// A value type with its type references flattened to indices.
fn lower_valtype(
v : @wasm_types.ValType[@type_store.Id],
) -> @wasm_types.ValType[Int32] {
match v {
I32 => I32
I64 => I64
F32 => F32
F64 => F64
V128 => V128
Ref(r) => Ref(lower_reftype(r))
// The store never holds an alias: it interns what one stands for.
Alias(_) => abort("a type alias reached the type store")
}
}
///|
fn lower_reftype(
r : @wasm_types.RefType[@type_store.Id],
) -> @wasm_types.RefType[Int32] {
{ nullable: r.nullable, typ: lower_heaptype(r.typ), }
}
///|
fn lower_heaptype(
h : @wasm_types.HeapType[@type_store.Id],
) -> @wasm_types.HeapType[Int32] {
match h {
Func => Func
NoFunc => NoFunc
Exn => Exn
NoExn => NoExn
Cont => Cont
NoCont => NoCont
Extern => Extern
NoExtern => NoExtern
Any => Any
Eq => Eq
I31 => I31
Struct => Struct
Array => Array
None_ => None_
Type(i) => Type(emitted_type_index(i.to_int_for_tests_only()))
Exact(i) => Exact(emitted_type_index(i.to_int_for_tests_only()))
}
}
///|
fn lower_storagetype(
s : @wasm_types.StorageType[@type_store.Id],
) -> @wasm_types.StorageType[Int32] {
match s {
Value(v) => Value(lower_valtype(v))
Packed(p) => Packed(p)
}
}
///|
/// Whether two definitions were written the same: an alias is the same alias
/// wherever its name was written, so its location does not take part.
fn same_written(a : @ast.SubType, b : @ast.SubType) -> Bool {
fn v(t : @wasm_types.ValType[@ast.Ident]) -> @wasm_types.ValType[@ast.Ident] {
match t {
Alias(n) => Alias({ name: n.name, loc: @basic.dummy_loc, })
_ => t
}
}
fn storage(
t : @wasm_types.StorageType[@ast.Ident],
) -> @wasm_types.StorageType[@ast.Ident] {
match t {
Value(x) => Value(v(x))
Packed(_) => t
}
}
fn norm(st : @ast.SubType) -> @ast.SubType {
let typ : @ast.CompType = match st.typ {
Func(ft) =>
Func({
params: ft.params.map(p => { ..p, desc: (p.desc.0, v(p.desc.1)), }),
results: ft.results.map(v),
})
Struct(fields) =>
Struct(
fields.map(f => {
..f,
desc: (f.desc.0, { ..f.desc.1, typ: storage(f.desc.1.typ), }),
}),
)
Array(f) => Array({ ..f, typ: storage(f.typ), })
Cont(_) => st.typ
}
{ ..st, typ, }
}
norm(a) == norm(b)
}