@yabgu_gm/tensionscript 0.11.4 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1763,6 +1763,8 @@ declare module "types:tensionscript/src/common" {
1763
1763
  const Object = "Object";
1764
1764
  const Array = "Array";
1765
1765
  const StaticArray = "StaticArray";
1766
+ /** The descriptor behind `@managed @dynamic [T * n]` (Landing 4d.1). */
1767
+ const DynamicArray = "DynamicArray";
1766
1768
  const Set = "Set";
1767
1769
  const Map = "Map";
1768
1770
  const Function = "Function";
@@ -3082,7 +3084,83 @@ declare module "types:tensionscript/src/resolver" {
3082
3084
  * collection wants `Array<T>`, and letting `T[N]` hold references would make
3083
3085
  * `@unmanaged` records carry GC edges they cannot describe. Refused by name.
3084
3086
  */
3087
+ /**
3088
+ * Turns on `Feature.MultiValue` the first time a value-form `[T * N]` return
3089
+ * type is resolved.
3090
+ *
3091
+ * **At resolution, not at emission.** The module's feature flags are computed
3092
+ * once, in `Compiler`'s constructor (`module.setFeatures`), and a return is
3093
+ * emitted long after that — so this sets the option *and* re-sets the module's
3094
+ * flags, which `setFeatures` supports (`module.ts`). Measuring that
3095
+ * re-settability is what this hook rests on: with a write-once flags word this
3096
+ * would have needed a deferred second pass.
3097
+ *
3098
+ * Policy is **auto-enable** rather than a required flag: a consumer who writes
3099
+ * `function make(): [T * N]` should not have to know that the emitter lowers it
3100
+ * to multivalue results. The trade is that the module's declared feature set
3101
+ * depends on its source, which is exactly the contract `--exportRuntime`
3102
+ * already sets. A module that never returns one never gets the flag.
3103
+ *
3104
+ * Idempotent: `features` is a bitmask, and re-setting the same flags is a
3105
+ * no-op for binaryen.
3106
+ */
3107
+ private enableMultiValueForArrayReturn;
3108
+ /** Whether `type` is a fixed array in its **value** form — N inline elements
3109
+ * with no address of their own — as opposed to the `@unmanaged` view (a
3110
+ * pointer) or the `@managed` heap form (Landing 5c). */
3111
+ private isValueFormFixedArray;
3085
3112
  private resolveFixedArrayType;
3113
+ /**
3114
+ * Resolves an {@link UnmanagedArrayTypeNode} to a
3115
+ * {@link TypeKind.UnmanagedArray}.
3116
+ *
3117
+ * Deliberately **not** a copy of {@link resolveFixedArrayType}: none of that
3118
+ * function's refusals belong here, and copying them would refuse the form's
3119
+ * whole purpose.
3120
+ *
3121
+ * A fixed array *contains* its elements, so a record element would claim `N`
3122
+ * records sit inline -- refused by name (`resolver.ts`, the `AS100` record
3123
+ * arm). An unmanaged array contains one address, so a record element is the
3124
+ * ordinary case: `[BodyState *]` is a pointer to a run of records, which is
3125
+ * exactly what the collision solver needs and what `BodyState[N]` cannot say.
3126
+ *
3127
+ * What *is* refused is an element with no bytes to point at, which is the same
3128
+ * `byteSize` question `sizeof` asks. An `@inline` element is admitted here on
3129
+ * purpose: it is a value type with a layout, and a pointer to it is a pointer
3130
+ * to those bytes -- the refusal that applies to it is the *field* form's, not
3131
+ * the pointer's.
3132
+ *
3133
+ * A **GC-managed** element is refused (see the arm below). Measured before it
3134
+ * existed: `[Foo *]` over a `class Foo` compiled and emitted
3135
+ * `base + i*4`, `i32.load`, `call Foo#get:x` -- the buffer holds bare
3136
+ * references that nothing scans.
3137
+ */
3138
+ private resolveUnmanagedArrayType;
3139
+ /**
3140
+ * Resolves a {@link DynamicArrayTypeNode} to a {@link TypeKind.DynamicArray}.
3141
+ *
3142
+ * The type is **one pointer** to a `[u32 length][pad][data]` header; the length
3143
+ * is not in the type, which is the whole point — it is a runtime value, read
3144
+ * from the buffer. `n` is spelling here and a binding where a function scope
3145
+ * exists (see `Program#bindDynamicLengthLocals`).
3146
+ *
3147
+ * Three refusals, each for a different reason:
3148
+ *
3149
+ * - **`@managed`** is Landing 4d. The buffer would need a collector-visible
3150
+ * header, which this landing does not produce.
3151
+ *
3152
+ * - **No model at all** cannot be honoured by any emitter: an inline
3153
+ * runtime-length array would need variable stack allocation, which WASM does
3154
+ * not have. Naming a model is the source saying where the bytes live.
3155
+ *
3156
+ * - **A GC-managed element** is the same hazard Landing 3's CX1 closed for
3157
+ * `[T *]`: the buffer is not scanned, so a reference stored in it is not a
3158
+ * root.
3159
+ *
3160
+ * An `@inline` element is accepted: it is a value type, so its extent is
3161
+ * `byteSize` and Landing 3 measured that stride works.
3162
+ */
3163
+ private resolveDynamicArrayType;
3086
3164
  private resolveTupleType;
3087
3165
  private resolveBuiltinNotNullableType;
3088
3166
  /** Resolves a type name to the program element it refers to. */
@@ -3195,6 +3273,28 @@ declare module "types:tensionscript/src/resolver" {
3195
3273
  private lookupPropertyAccessExpression;
3196
3274
  /** Resolves a property access expression to its static type. */
3197
3275
  private resolvePropertyAccessExpression;
3276
+ /**
3277
+ * The element type of an array-kind receiver, or `null` when `type` is not one.
3278
+ *
3279
+ * Three kinds answer here, and they are one family even though only two are a
3280
+ * `TypeKind`:
3281
+ *
3282
+ * - `[T *]` — `TypeKind.UnmanagedArray`, element on the type;
3283
+ * - `@unmanaged @dynamic [T * n]` — `TypeKind.DynamicArray`, element on the type;
3284
+ * - `@managed @dynamic [T * n]` — a **class reference** to the std
3285
+ * `DynamicArray<T>` descriptor (Landing 4d.1), so the element is the class's
3286
+ * type argument.
3287
+ *
3288
+ * The third case is why this is a helper. Landing 2 added the first arm by
3289
+ * hand, Landing 4b added the second by hand, and the managed form would be the
3290
+ * third copy of the same two-line idea — in two places, since the element is
3291
+ * wanted both as a *type* (`resolveElementAccessExpression`) and as an
3292
+ * *element* (`lookupElementAccessExpression`, which peels `y[i].field` through
3293
+ * the element's class). One definition, three kinds, two consumers.
3294
+ */
3295
+ private arrayKindElementType;
3296
+ /** Whether `classReference` is the std `DynamicArray<T>` descriptor class. */
3297
+ private isManagedDynamicArrayClass;
3198
3298
  /** Looks up the program element the specified element access expression refers to. */
3199
3299
  private lookupElementAccessExpression;
3200
3300
  /** Resolves an element access expression to its static type. */
@@ -3465,6 +3565,25 @@ declare module "types:tensionscript/src/parser" {
3465
3565
  finish(): void;
3466
3566
  /** Parses a type name. */
3467
3567
  parseTypeName(tn: Tokenizer): TypeName | null;
3568
+ /**
3569
+ * Whether a `[` at the start of a type begins the unmanaged-array family
3570
+ * (`[T *]`, `[T * N]`) rather than a tuple.
3571
+ *
3572
+ * The discriminator is one token after the element type: `*` commits, and
3573
+ * anything else (`]`, `,`, `:`, `|`) belongs to the tuple arm. The lookahead
3574
+ * speculates with errors suppressed and rewinds either way -- the same shape
3575
+ * as `tryParseFunctionType` / `tryParseSignatureIsSignature`, and the reason
3576
+ * the tuple arm's body needs no change: it is entered with the tokenizer
3577
+ * exactly where its own `tn.skip(Token.CloseBracket)` expects it.
3578
+ *
3579
+ * A pre-type decorator is excluded before speculating. `parseType`'s decorator
3580
+ * arm reports `AS212` ("Decorator '@x' is not valid here") *before*
3581
+ * `suppressErrors` is consulted, so speculating into `[@unmanaged T *]` would
3582
+ * emit a diagnostic for a form this family does not accept anyway -- and then
3583
+ * emit the tuple/`Type expected` error on the rewind, giving two errors for
3584
+ * one mistake.
3585
+ */
3586
+ startsUnmanagedArrayType(tn: Tokenizer): boolean;
3468
3587
  /** Parses a type. */
3469
3588
  parseType(tn: Tokenizer, acceptParenthesized?: boolean, suppressErrors?: boolean): TypeNode | null;
3470
3589
  private tryParseSignatureIsSignature;
@@ -3636,7 +3755,7 @@ declare module "types:tensionscript/src/program" {
3636
3755
  import { Type, Signature } from "types:tensionscript/src/types";
3637
3756
  import { Token } from "types:tensionscript/src/tokenizer";
3638
3757
  import { Node, Source, DecoratorNode, DecoratorKind, TypeParameterNode, TypeNode, NamedTypeNode, FunctionTypeNode, ArrowKind, Expression, IdentifierExpression, Statement, ClassDeclaration, DeclarationStatement, EnumDeclaration, EnumValueDeclaration, FieldDeclaration, FunctionDeclaration, InterfaceDeclaration, NamespaceDeclaration, TypeDeclaration, VariableDeclaration, VariableLikeDeclarationStatement } from "types:tensionscript/src/ast";
3639
- import { Module, ExpressionRef, FunctionRef, MemorySegment } from "types:tensionscript/src/module";
3758
+ import { Module, ExpressionRef, FunctionRef, MemorySegment, TypeRef } from "types:tensionscript/src/module";
3640
3759
  import { Resolver } from "types:tensionscript/src/resolver";
3641
3760
  import { Flow } from "types:tensionscript/src/flow";
3642
3761
  import { Parser } from "types:tensionscript/src/parser";
@@ -3764,6 +3883,17 @@ declare module "types:tensionscript/src/program" {
3764
3883
  /** Gets the standard `StaticArray` prototype. */
3765
3884
  get staticArrayPrototype(): ClassPrototype;
3766
3885
  private _staticArrayPrototype;
3886
+ /**
3887
+ * Gets the `DynamicArray` prototype — the descriptor class behind
3888
+ * `@managed @dynamic [T * n]`.
3889
+ *
3890
+ * Follows `staticArrayPrototype` exactly: a lazily-cached `require` of the
3891
+ * std prototype, so `@managed @dynamic [T * n]` can resolve through
3892
+ * `resolveClass(program.dynamicArrayPrototype, [elementType])` and come back
3893
+ * as an ordinary generic class instance.
3894
+ */
3895
+ get dynamicArrayPrototype(): ClassPrototype;
3896
+ private _dynamicArrayPrototype;
3767
3897
  /** Gets the standard `Set` prototype. */
3768
3898
  get setPrototype(): ClassPrototype;
3769
3899
  private _setPrototype;
@@ -4409,6 +4539,29 @@ declare module "types:tensionscript/src/program" {
4409
4539
  * therefore cannot alias.
4410
4540
  */
4411
4541
  fieldLocals: Map<string, Local> | null;
4542
+ /**
4543
+ * The WASM slot type for this local, when it is **not** what {@link type}
4544
+ * says it is.
4545
+ *
4546
+ * `type` answers "what does the source see" — a record, so `p.field` resolves;
4547
+ * an array, so `arr[i]` resolves. The slot is a different question, and for
4548
+ * most locals the two agree. Three cases where they do not:
4549
+ *
4550
+ * - **view local** (`isViewLocal`) — the slot is the pointer; handled below by
4551
+ * substituting `usizeType`, because a pointer *is* a type.
4552
+ * - **multivalue tuple** — the slot is the `TypeRef` binaryen makes for a
4553
+ * multi-result signature. That has **no `Type` at all**: `createType(refs)`
4554
+ * answers a `TypeRef` the type system cannot describe. Hence an override
4555
+ * rather than a substitution — this is the one local whose slot cannot be
4556
+ * named by a `Type`.
4557
+ *
4558
+ * {@link TypeRef.None} for every other local, so the ordinary case costs
4559
+ * nothing — the same reasoning as {@link fieldLocals}. A sentinel rather than
4560
+ * `| null` because `TypeRef` is an integer type and cannot be nullable here
4561
+ * (measured: `AS204: Type 'TypeRef' cannot be nullable` when the self-hosted
4562
+ * compiler parsed this declaration).
4563
+ */
4564
+ typeRefOverride: TypeRef;
4412
4565
  /**
4413
4566
  * Whether this local is a **view parameter**: `a: @unmanaged T` where `T` is
4414
4567
  * an inline type.
@@ -4430,6 +4583,15 @@ declare module "types:tensionscript/src/program" {
4430
4583
  * is what gives it one `i32` slot instead of the record's reference type.
4431
4584
  */
4432
4585
  isViewLocal: boolean;
4586
+ /**
4587
+ * Whether this local is a **dynamic-array length binding** — the `n` in
4588
+ * `[T * n]`, introduced by the signature rather than written as a declaration.
4589
+ *
4590
+ * It is read-only by construction: its value comes from the buffer header at
4591
+ * function entry, so assigning it would make the compiler's idea of the length
4592
+ * disagree with the buffer's. The assignment and shadowing rules key on this.
4593
+ */
4594
+ isDynamicLength: boolean;
4433
4595
  /** Constructs a new local variable. */
4434
4596
  constructor(
4435
4597
  /** Simple name. */
@@ -4555,6 +4717,23 @@ declare module "types:tensionscript/src/program" {
4555
4717
  * one structure regardless of where the leaves' storage came from.
4556
4718
  */
4557
4719
  bindSroaParameterLeaves(type: Type, path: string, into: Map<string, Local>, localIndex: number, flow: Flow): BindResult;
4720
+ /**
4721
+ * Binds a value-form `[T * N]` parameter's **leaves** to the WASM parameters
4722
+ * that carry them.
4723
+ *
4724
+ * The array sibling of {@link bindSroaParameterLeaves}, and the same tree
4725
+ * shape `fillSroaArrayLocals` builds for a local: the map is keyed `"0"`,
4726
+ * `"1"`, ... and a record element is a placeholder carrying its field leaves.
4727
+ * What differs is where the leaves' storage comes from — incoming parameters
4728
+ * here, fresh locals there.
4729
+ *
4730
+ * Element-major, matching `Signature.leafTypesOf` and the caller's operand
4731
+ * order. A parameter is a **value** for the callee's whole body: nothing is
4732
+ * written back, so a store to `arr[0].x` is invisible to the caller. That is
4733
+ * the copy semantics the representation is for, and it costs nothing extra —
4734
+ * the leaves are separate slots precisely because the value has no address.
4735
+ */
4736
+ bindSroaArrayParameterLeaves(type: Type, path: string, into: Map<string, Local>, localIndex: number, flow: Flow): BindResult;
4558
4737
  /** Constructs a new concrete function. */
4559
4738
  constructor(
4560
4739
  /** Name incl. type parameters, i.e. `foo<i32>`. */
@@ -4567,8 +4746,23 @@ declare module "types:tensionscript/src/program" {
4567
4746
  signature: Signature, // pre-resolved
4568
4747
  /** Contextual type arguments inherited from its parent class, if any. */
4569
4748
  contextualTypeArguments?: Map<string, Type> | null);
4570
- /** Gets the types of additional locals that are not parameters. */
4571
- getNonParameterLocalTypes(): Type[];
4749
+ /**
4750
+ * The **WASM slot types** of the additional locals that are not parameters.
4751
+ *
4752
+ * Named for the refs rather than the types because that is what it now
4753
+ * answers: most slots are `local.type.toRef()`, but a multivalue tuple's slot
4754
+ * is a `TypeRef` with no `Type` behind it ({@link Local.typeRefOverride}), and
4755
+ * a view local's slot is the pointer rather than the record it names. Callers
4756
+ * feed this straight to `Module#addFunction`'s `varTypes`, which is a
4757
+ * `TypeRef[]` — so the conversion belongs here, where the slot is decided,
4758
+ * rather than at each of them.
4759
+ *
4760
+ * Renamed from `getNonParameterLocalTypes` when the tuple override landed:
4761
+ * four call sites all wrapped the old answer in `typesToRefs`, and renaming
4762
+ * means a caller that was missed fails to compile instead of silently
4763
+ * declaring a slot of the wrong type.
4764
+ */
4765
+ getNonParameterLocalTypeRefs(): TypeRef[];
4572
4766
  /** Gets the name of the parameter at the specified index. */
4573
4767
  getParameterName(index: number): string;
4574
4768
  /** Creates a stub for use with this function, i.e. for varargs or override calls. */
@@ -4886,7 +5080,37 @@ declare module "types:tensionscript/src/types" {
4886
5080
  /** String iterator. */
4887
5081
  StringviewIter = 25,
4888
5082
  /** No return type. */
4889
- Void = 26
5083
+ Void = 26,
5084
+ /**
5085
+ * A pointer to `T` with unknown length, `[T *]`.
5086
+ *
5087
+ * **Appended at the end**, not beside {@link FixedArray}, on purpose. This is
5088
+ * a `const enum` with implicit numbering, so an insert would renumber the
5089
+ * twelve kinds between `FixedArray` and `Void`; the compiler is self-hosted, so
5090
+ * a partial rebuild could then disagree with `dist/` about what
5091
+ * `TypeKind.Extern` means -- silently. The order here carries no meaning.
5092
+ *
5093
+ * One slot holding an address: not a value (no extent to inline), not a
5094
+ * reference (the collector owns nothing). `toRef` answers the pointer type,
5095
+ * which is what lets a `[T *]` parameter, local, global and return bind as one
5096
+ * slot where `T[N]` reaches `Type#toRef`'s `assert(false)`.
5097
+ */
5098
+ UnmanagedArray = 27,
5099
+ /**
5100
+ * `@unmanaged @dynamic [T * n]` — a header-prefixed array whose length is a
5101
+ * runtime value.
5102
+ *
5103
+ * Appended for the same reason {@link UnmanagedArray} was: an insert would
5104
+ * renumber every kind after it, and the compiler is self-hosted.
5105
+ *
5106
+ * A separate kind rather than a flag on `UnmanagedArray` because the two have
5107
+ * different layouts — one addresses bare elements, the other a
5108
+ * `[u32 length][pad][data]` header — and every element access has to know
5109
+ * which it is looking at. The storage *model* is a field
5110
+ * ({@link dynamicMemoryModel}), not a kind, so `@managed @dynamic` (Landing 4d)
5111
+ * is the same kind under a different owner.
5112
+ */
5113
+ DynamicArray = 28
4890
5114
  }
4891
5115
  /** Indicates capabilities of a type. */
4892
5116
  export const enum TypeFlags {
@@ -4937,12 +5161,55 @@ declare module "types:tensionscript/src/types" {
4937
5161
  /** Element count of a {@link TypeKind.FixedArray}, else `0`. */
4938
5162
  fixedLength: number;
4939
5163
  /**
4940
- * Alignment (log2) of a {@link TypeKind.FixedArray}: its element's.
5164
+ * Who owns a {@link TypeKind.FixedArray}'s storage — the second axis of the
5165
+ * kind, alongside the element and the length.
5166
+ *
5167
+ * The memory model was already on the AST (`TypeNode.memoryModel`) and was
5168
+ * simply dropped before Landing 5a.5: `Type.fixedArray` interned one object
5169
+ * per `(element, length)`, so `[u32 * 4]`, `@unmanaged [u32 * 4]`,
5170
+ * `@managed [u32 * 4]` and `@inline [u32 * 4]` were **the same Type**, and
5171
+ * the prefix was decorative — measured, all four emitted identical code.
5172
+ *
5173
+ * The two forms are not the same thing. `[u32 * 4]` is 16 bytes of inline
5174
+ * elements with no address of its own; `@unmanaged [u32 * 4]` is a pointer to
5175
+ * four elements, i.e. 4 bytes. Part of {@link equals} for the same reason
5176
+ * {@link dynamicMemoryModel} is: interning them together would make the two
5177
+ * spellings the same object, and no downstream test could tell them apart.
5178
+ */
5179
+ fixedMemoryModel: MemoryModelCode;
5180
+ /**
5181
+ * Element type of a {@link TypeKind.UnmanagedArray}, else `null`.
5182
+ *
5183
+ * Kept apart from {@link fixedElementType} rather than shared. The two kinds
5184
+ * say different things -- an extent versus an address -- and a shared field
5185
+ * would force every `kind == FixedArray` test in the compiler to become a
5186
+ * three-way test, with the risk that one of them silently starts treating an
5187
+ * address as an extent. `size` differs for the same reason: this kind's size is
5188
+ * the pointer's, not the element's replicated.
5189
+ */
5190
+ unmanagedElementType: Type | null;
5191
+ /** Element type of a {@link TypeKind.DynamicArray}, else `null`. */
5192
+ dynamicArrayElementType: Type | null;
5193
+ /**
5194
+ * Who owns a {@link TypeKind.DynamicArray}'s buffer.
5195
+ *
5196
+ * {@link MemoryModelCode.Unmanaged} for a C-compatible header-prefixed buffer;
5197
+ * {@link MemoryModelCode.Managed} is Landing 4d. Part of {@link equals}, so the
5198
+ * two cannot be confused for one another even though they share a kind and a
5199
+ * layout.
5200
+ */
5201
+ dynamicMemoryModel: MemoryModelCode;
5202
+ /**
5203
+ * Alignment (log2) of a {@link TypeKind.FixedArray}.
5204
+ *
5205
+ * For the **value** form this is the element's. `alignLog2` cannot derive
5206
+ * that from `byteSize` -- a 24-byte `f32[6]` would report 16-byte alignment
5207
+ * (`31 - clz(24)` = 4), and a 96-byte `f32[24]` would report 64. An array is
5208
+ * aligned as its element is, no matter how long it is, so the value is carried
5209
+ * rather than computed.
4941
5210
  *
4942
- * `alignLog2` cannot derive this from `byteSize` -- a 24-byte `f32[6]` would
4943
- * report 16-byte alignment (`31 - clz(24)` = 4), and a 96-byte `f32[24]` would
4944
- * report 64. An array is aligned as its element is, no matter how long it is,
4945
- * so the value is carried rather than computed.
5211
+ * For the **view** form it is the *pointer's*, because that is what the value
5212
+ * is: the extent says nothing about how the address is aligned.
4946
5213
  */
4947
5214
  private fixedAlignLog2;
4948
5215
  /** Respective non-nullable type, if nullable. */
@@ -4959,7 +5226,15 @@ declare module "types:tensionscript/src/types" {
4959
5226
  get exceptVoid(): Type;
4960
5227
  /** Size in bytes. */
4961
5228
  get byteSize(): number;
4962
- /** Gets this type's logarithmic alignment in memory. */
5229
+ /**
5230
+ * Gets this type's logarithmic alignment in memory.
5231
+ *
5232
+ * An `UnmanagedArray` deliberately does **not** take the {@link fixedAlignLog2}
5233
+ * branch. It is one pointer, so its alignment is the pointer's -- which is what
5234
+ * the `byteSize` fallback already yields, since this kind's `size` is the
5235
+ * pointer's and not the element's replicated. There is no element alignment to
5236
+ * borrow here; the element is not stored in this type.
5237
+ */
4963
5238
  get alignLog2(): number;
4964
5239
  /** Tests if this type represents a basic value. */
4965
5240
  get isValue(): boolean;
@@ -5010,6 +5285,34 @@ declare module "types:tensionscript/src/types" {
5010
5285
  get isManaged(): boolean;
5011
5286
  /** Tests if this is a class type explicitly annotated as unmanaged. */
5012
5287
  get isUnmanaged(): boolean;
5288
+ /**
5289
+ * This type's extent in bytes when it is stored **inline**, rather than by
5290
+ * reference.
5291
+ *
5292
+ * For a scalar the two are the same number, and for an ordinary class neither
5293
+ * is what an inline array wants -- a class element is stored as its 4-byte
5294
+ * reference, so `byteSize` is right there. The one case where `byteSize` is
5295
+ * **wrong** is a C-layout record (`@unmanaged` / `@managed struct`, and
5296
+ * `@inline struct`, which the parser also flags `@unmanaged`): there
5297
+ * `byteSize` is the reference width (4) while the bytes an inline element
5298
+ * actually occupies are the record's layout size, `Class#nextMemoryOffset`.
5299
+ *
5300
+ * That is the same pair `builtin_sizeof` distinguishes
5301
+ * (`builtins.ts`, the `Unmanaged | ManagedStruct` branch), and it is why
5302
+ * `sizeof<Props>()` answered 16 while `sizeof<[Props * 4]>()` answered 16
5303
+ * instead of 64: the array's size was built from the reference width.
5304
+ */
5305
+ get inlineExtentBytes(): number;
5306
+ /**
5307
+ * This type's alignment as a log2 when it is stored inline.
5308
+ *
5309
+ * A record's alignment is the strongest of its fields', accumulated while the
5310
+ * resolver lays it out (`Class#alignLog2`), and **not** `Type#alignLog2` —
5311
+ * which reads the class's 4-byte reference size and would report 2 for every
5312
+ * record, including one aligned to 8. The same distinction `builtin_alignof`
5313
+ * makes, for the same reason.
5314
+ */
5315
+ get inlineAlignLog2(): number;
5013
5316
  get isMemory(): boolean;
5014
5317
  /** Gets the corresponding non-nullable type. */
5015
5318
  get nonNullableType(): Type;
@@ -5084,21 +5387,90 @@ declare module "types:tensionscript/src/types" {
5084
5387
  /** A 32-bit float. */
5085
5388
  static readonly f32: Type;
5086
5389
  /** A 64-bit float. */
5087
- /** Interned fixed-array types, keyed by element type and length. */
5390
+ /** Interned fixed-array types, keyed by model, element type and length. */
5088
5391
  private static _fixedArrays;
5089
5392
  /**
5090
- * Gets the {@link TypeKind.FixedArray} of `elementType` repeated `length` times.
5091
- *
5092
- * Interned: the same element and length always yield the same `Type`, so
5093
- * `offsetof`, `alignof`, `sizeof` and field lookup can compare by identity the
5094
- * way they do for every other type. Size is the element's byte size times the
5095
- * length, and the alignment is the element's -- carried in
5096
- * {@link fixedAlignLog2} rather than derived from the total, because a total
5097
- * is not a power of two for the lengths records actually have (`f32[6]`).
5393
+ * Gets the {@link TypeKind.FixedArray} of `elementType` repeated `length`
5394
+ * times, in the storage named by `memoryModel`.
5395
+ *
5396
+ * Interned: the same `(model, element, length)` always yields the same `Type`,
5397
+ * so `offsetof`, `alignof`, `sizeof` and field lookup can compare by identity
5398
+ * the way they do for every other type.
5399
+ *
5400
+ * **The model is part of the identity**, and that is the point of Landing
5401
+ * 5a.5. Before it, this function had one axis (element, length) and the memory
5402
+ * model was dropped, so `[u32 * 4]` and `@unmanaged [u32 * 4]` were the same
5403
+ * Type object — measured: both compiled, both `sizeof` 16, and all four
5404
+ * spellings (`[T * N]`, `@unmanaged [T * N]`, `@managed [T * N]`,
5405
+ * `@inline [T * N]`) emitted byte-identical code. A prefix that changes
5406
+ * nothing is worse than one that is refused, because it reads as if it means
5407
+ * something.
5408
+ *
5409
+ * The two honourable forms differ in both size and alignment:
5410
+ *
5411
+ * value (`Any` / `Inline`) — inline elements: `elementSize * length` bytes,
5412
+ * aligned as the element is
5413
+ * view (`Unmanaged`) — one address: `pointerSize` bits, aligned as a
5414
+ * pointer is
5415
+ * heap (`Managed`) — one GC reference, same shape as the view; the
5416
+ * form itself is refused until Landing 5c, but
5417
+ * the Type can be built so that `sizeof` answers
5418
+ * the truth before the refusal fires
5419
+ *
5420
+ * `pointerSize` is in **bits**, matching `Type.size`.
5098
5421
  */
5099
- static fixedArray(elementType: Type, length: number): Type;
5422
+ static fixedArray(elementType: Type, length: number, memoryModel: MemoryModelCode, pointerSize: number): Type;
5100
5423
  /** Gets the element type of a {@link TypeKind.FixedArray}. */
5101
5424
  get fixedArrayElementType(): Type;
5425
+ /** Interned unmanaged-array types, keyed by element type and pointer width. */
5426
+ private static _unmanagedArrays;
5427
+ /**
5428
+ * Gets the {@link TypeKind.UnmanagedArray} of `elementType`: a pointer to a run
5429
+ * of `elementType`, with no length.
5430
+ *
5431
+ * `pointerSize` is `options.usizeType.size` -- 32 or 64 bits. It is a
5432
+ * parameter rather than a constant because a `Type` carries its own size, and
5433
+ * `size` here must be the **pointer's**, not the element's: there is no extent
5434
+ * to measure. It is part of the cache key for the same reason -- `equals`
5435
+ * compares element types only, so two types differing solely in pointer width
5436
+ * must not be the same object.
5437
+ *
5438
+ * Unlike {@link Type.fixedArray} this consults nothing about the element but
5439
+ * its identity: the count is unknown, which is the entire point of the kind.
5440
+ *
5441
+ * The cache is validated by **identity** of the element, not just by the key.
5442
+ * This is what keeps interning a per-program property rather than a
5443
+ * per-process one: the map is a static that outlives a `Program`, so a second
5444
+ * `asc.main` in the same process (which is exactly what the compiler test
5445
+ * harness does -- debug then release) would otherwise be handed the first
5446
+ * program's `Type`, whose `unmanagedElementType` carries the first program's
5447
+ * `Class`. A property read through that class then emitted a call to a getter
5448
+ * the new module never compiled, and the module failed to validate:
5449
+ * `wasm-validator error ... call target must exist`.
5450
+ *
5451
+ * So a cached entry is returned only when its element is the *same object* the
5452
+ * current program just resolved. Within one program that is always true --
5453
+ * `resolveType` interns a record's `Type` -- so interning still holds there.
5454
+ */
5455
+ static unmanagedArray(elementType: Type, pointerSize: number): Type;
5456
+ /** Gets the element type of a {@link TypeKind.UnmanagedArray}. */
5457
+ get unmanagedArrayElementType(): Type;
5458
+ /** Interned dynamic-array types, keyed by model, element and pointer width. */
5459
+ private static _dynamicArrays;
5460
+ /**
5461
+ * Gets the {@link TypeKind.DynamicArray} of `elementType`.
5462
+ *
5463
+ * The key carries the **model** as well as the element, because `equals`
5464
+ * compares both: two models over one element are different types, and
5465
+ * interning them together would make them the same object.
5466
+ *
5467
+ * Like {@link Type.unmanagedArray} the entry is validated by element identity
5468
+ * on the way out — the map outlives a `Program`, so a second compile in one
5469
+ * process must not be handed the first one's element `Class`.
5470
+ */
5471
+ static dynamicArray(elementType: Type, memoryModel: MemoryModelCode, pointerSize: number): Type;
5472
+ /** Gets the element type of a {@link TypeKind.DynamicArray}. */
5473
+ get dynamicElementType(): Type;
5102
5474
  static readonly f64: Type;
5103
5475
  /** A 128-bit vector. */
5104
5476
  static readonly v128: Type;
@@ -7778,7 +8150,26 @@ declare module "types:tensionscript/src/ast" {
7778
8150
  ExportMember = 67,
7779
8151
  SwitchCase = 68,
7780
8152
  IndexSignature = 69,
7781
- Comment = 70
8153
+ Comment = 70,
8154
+ /**
8155
+ * `[T *]` — a pointer to T with unknown length.
8156
+ *
8157
+ * Appended at the end rather than beside {@link FixedArrayType}. `NodeKind`
8158
+ * is a `const enum` with implicit numbering, so an insert would renumber every
8159
+ * node kind after it; the compiler is self-hosted, so a partial rebuild could
8160
+ * then disagree with `dist/` about what `NodeKind.Comment` means -- silently.
8161
+ * The position carries no meaning.
8162
+ */
8163
+ UnmanagedArrayType = 71,
8164
+ /**
8165
+ * `@unmanaged @dynamic [T * n]` — a header-prefixed array whose length is a
8166
+ * runtime value read from the buffer.
8167
+ *
8168
+ * Appended for the same reason {@link UnmanagedArrayType} was: this is an
8169
+ * implicit-numbering `const enum`, so an insert would renumber every node kind
8170
+ * after it.
8171
+ */
8172
+ DynamicArrayType = 72
7782
8173
  }
7783
8174
  /** Base class of all nodes. */
7784
8175
  export abstract class Node {
@@ -7796,6 +8187,8 @@ declare module "types:tensionscript/src/ast" {
7796
8187
  static createFunctionType(parameters: ParameterNode[], returnType: TypeNode, explicitThisType: NamedTypeNode | null, isNullable: boolean, range: Range): FunctionTypeNode;
7797
8188
  static createTupleType(elements: TypeNode[], elementNames: (IdentifierExpression | null)[] | null, isNullable: boolean, range: Range): TupleTypeNode;
7798
8189
  static createFixedArrayType(elementType: TypeNode, length: number, range: Range): FixedArrayTypeNode;
8190
+ static createUnmanagedArrayType(elementType: TypeNode, range: Range): UnmanagedArrayTypeNode;
8191
+ static createDynamicArrayType(elementType: TypeNode, lengthName: IdentifierExpression, memoryModel: MemoryModel, range: Range): DynamicArrayTypeNode;
7799
8192
  static createOmittedType(range: Range): NamedTypeNode;
7800
8193
  static createTypeParameter(name: IdentifierExpression, extendsType: NamedTypeNode | null, defaultType: NamedTypeNode | null, range: Range): TypeParameterNode;
7801
8194
  static createParameter(parameterKind: ParameterKind, name: IdentifierExpression, type: TypeNode, initializer: Expression | null, range: Range): ParameterNode;
@@ -8035,6 +8428,56 @@ declare module "types:tensionscript/src/ast" {
8035
8428
  /** Source range. */
8036
8429
  range: Range);
8037
8430
  }
8431
+ /**
8432
+ * Represents an unmanaged array type, `[T *]` — a pointer to `T` with no length.
8433
+ *
8434
+ * The difference from {@link FixedArrayTypeNode} is the missing `length`, and it
8435
+ * is the whole difference: a fixed array *contains* its elements, so it has an
8436
+ * extent the layout can size; this one *addresses* them, so its own size is the
8437
+ * pointer's. That is why `[T *]` binds as one `i32` slot where `T[N]` has no
8438
+ * reference representation at all (`Type#toRef` asserts on a fixed array).
8439
+ *
8440
+ * Not a reference: the collector owns nothing here, the address is the caller's.
8441
+ * Not a value either: there is nothing to inline. `changetype` and a parameter
8442
+ * are the two ways one comes into being.
8443
+ */
8444
+ export class UnmanagedArrayTypeNode extends TypeNode {
8445
+ /** Element type. */
8446
+ elementType: TypeNode;
8447
+ constructor(
8448
+ /** Element type. */
8449
+ elementType: TypeNode,
8450
+ /** Source range. */
8451
+ range: Range);
8452
+ }
8453
+ /**
8454
+ * Represents a dynamic array type, `@unmanaged @dynamic [T * n]`.
8455
+ *
8456
+ * The `n` is **spelling, not an expression**: the parser cannot evaluate it (it
8457
+ * has no symbol table), so it is carried as a name and the length is read at
8458
+ * runtime from the buffer header. That is why the form is admissible at parse
8459
+ * time without knowing whether `n` is a constant — resolution decides that, and
8460
+ * `resolveDynamicArrayType` refuses a name that turns out to be compile-time.
8461
+ *
8462
+ * One i32 slot at the ABI, holding a pointer to `[u32 length][pad][T data[n]]`.
8463
+ */
8464
+ export class DynamicArrayTypeNode extends TypeNode {
8465
+ /** Element type. */
8466
+ elementType: TypeNode;
8467
+ /** The length's name. Bound as a read-only local where a function scope exists. */
8468
+ lengthName: IdentifierExpression;
8469
+ /** Storage model of the buffer. `Unmanaged` for now; `Managed` is Landing 4d. */
8470
+ dynamicMemoryModel: MemoryModel;
8471
+ constructor(
8472
+ /** Element type. */
8473
+ elementType: TypeNode,
8474
+ /** The length's name. Bound as a read-only local where a function scope exists. */
8475
+ lengthName: IdentifierExpression,
8476
+ /** Storage model of the buffer. `Unmanaged` for now; `Managed` is Landing 4d. */
8477
+ dynamicMemoryModel: MemoryModel,
8478
+ /** Source range. */
8479
+ range: Range);
8480
+ }
8038
8481
  /** Represents a type parameter. */
8039
8482
  export class TypeParameterNode extends Node {
8040
8483
  /** Identifier reference. */
@@ -10387,6 +10830,15 @@ declare module "types:tensionscript/src/compiler" {
10387
10830
  indexExpression: Expression | null,
10388
10831
  /** Whether to tee the value. */
10389
10832
  tee: boolean): ExpressionRef;
10833
+ /**
10834
+ * The {@link DynamicArrayTypeNode} a declaration names, if it names one.
10835
+ *
10836
+ * Two spellings carry the length's name: the annotation (`const y: [T * n]`)
10837
+ * and `changetype`'s type argument (`const y = changetype<[T * n]>(p)`). Both
10838
+ * are read here so the local-position binding does not depend on which one the
10839
+ * source chose.
10840
+ */
10841
+ private dynamicArrayTypeNodeOf;
10390
10842
  /** Makes an assignment to a local, keeping track of wrap and null states. */
10391
10843
  private makeLocalAssignment;
10392
10844
  /** Makes an assignment to a global. */
@@ -10459,6 +10911,177 @@ declare module "types:tensionscript/src/compiler" {
10459
10911
  /** Creates an indirect call to a first-class function. */
10460
10912
  makeCallIndirect(signature: Signature, functionArg: ExpressionRef, reportNode: Node, operands?: ExpressionRef[] | null, immediatelyDropped?: boolean): ExpressionRef;
10461
10913
  private compileCommaExpression;
10914
+ /**
10915
+ * The byte extent of one element of a `[T *]` — the stride `y[i]` walks by.
10916
+ *
10917
+ * For a scalar this is `Type.byteSize`. For a record it is **not**: an
10918
+ * `@unmanaged`/`@managed` record's `Type` is reference-like, so its `byteSize`
10919
+ * is the 4-byte address, and the extent lives on the `Class` that laid the
10920
+ * fields out. `builtin_sizeof` already answers this way for the same reason
10921
+ * (`builtins.ts`, the `Unmanaged | ManagedStruct` branch answers
10922
+ * `nextMemoryOffset`), so the two cannot disagree about how big a record is.
10923
+ *
10924
+ * An `@inline struct` needs no arm: it *is* a value type, so its
10925
+ * `byteSize` is already the extent. Measured — `[Inl *]` over
10926
+ * `@inline struct Inl { x: f64; y: f64 }` strides 16, which is
10927
+ * `sizeof<Inl>()`.
10928
+ *
10929
+ * Measured, and the reason this helper exists: using `byteSize` directly gave
10930
+ * a stride of 4 for a two-`f64` record of 16 bytes — the same "alignment or
10931
+ * reference size instead of the extent" mistake the fixed-array arm avoids
10932
+ * only because it refuses records outright.
10933
+ */
10934
+ private unmanagedArrayElementSize;
10935
+ /**
10936
+ * Whether `type` is a record whose elements live **at** the address rather
10937
+ * than being a value loaded from it: `@unmanaged`, `@managed` or `@inline`.
10938
+ *
10939
+ * This is the discriminator between "the address is the element" and "load the
10940
+ * element from the address", and it is deliberately about the *element kind*
10941
+ * rather than about type equality — see {@link compileUnmanagedArrayElementAccess}.
10942
+ */
10943
+ private isRecordValueType;
10944
+ /**
10945
+ * The header an `@unmanaged @dynamic [T * n]` buffer carries in front of its
10946
+ * elements: `align_up(4, alignof(T))`.
10947
+ *
10948
+ * Four bytes hold the `u32` length; anything needing stronger alignment than
10949
+ * that gets padding after it. So `u32` elements sit at `ptr + 4`, and a
10950
+ * two-`f64` record sits at `ptr + 8`.
10951
+ *
10952
+ * Reported in Landing 4a: **no header-size helper existed**, because nothing
10953
+ * in 4a needed one — `.length` reads offset 0, and 4a's own V5 recorded that
10954
+ * the constant could not be observed from outside. This is its first
10955
+ * definition; `.length` and the element address both go through it so they
10956
+ * cannot drift.
10957
+ *
10958
+ * A record's alignment is `Class#alignLog2` rather than `Type#alignLog2` for
10959
+ * the same reason its size is `nextMemoryOffset`: a record's `Type` is
10960
+ * reference-like and would report the 4-byte pointer's alignment for every
10961
+ * record, including the `f64`-only ones that are aligned 8.
10962
+ */
10963
+ /**
10964
+ * Whether `type` is the **managed** dynamic-array descriptor — the std
10965
+ * `DynamicArray<T>` class that `@managed @dynamic [T * n]` resolves to
10966
+ * (Landing 4d.1).
10967
+ *
10968
+ * Answered by class identity rather than by a memory-model field, because the
10969
+ * managed form is a class reference now: its `Type` is the class instance's
10970
+ * type, and the "model" is implicit in `isManaged`. That is the whole point of
10971
+ * the redesign — `TypeKind.DynamicArray` carries `TypeFlags.None` and is not
10972
+ * traced, so a descriptor stored as one would not be rooted.
10973
+ */
10974
+ /**
10975
+ * The element type of either dynamic-array form.
10976
+ *
10977
+ * `@unmanaged @dynamic [T * n]` carries the element on its type;
10978
+ * `@managed @dynamic [T * n]` is a `DynamicArray<T>` class reference, so the
10979
+ * element is the class's type argument. One accessor for both, because every
10980
+ * consumer below — access, compound, increment — would otherwise branch.
10981
+ */
10982
+ private dynamicArrayElementOf;
10983
+ private isManagedDynamicArrayType;
10984
+ private dynamicArrayHeaderSize;
10985
+ /**
10986
+ * Compiles the byte address of `y[i]` for a dynamic array:
10987
+ * `ptr + headerSize + i*sizeof(T)`.
10988
+ *
10989
+ * Shared by the read and the write arm, so the two cannot disagree about which
10990
+ * bytes they address — the same split the fixed and unmanaged arms use.
10991
+ *
10992
+ * Two constants, each of which was a bug somewhere else in this family:
10993
+ *
10994
+ * - **`headerSize` is present.** It is what separates a dynamic array from a
10995
+ * `[T *]`: the data starts after the length word and its padding.
10996
+ * - **The stride is a multiply by the element's extent**, not a shift by its
10997
+ * alignment. For a two-`f64` record those differ (16 vs 8), which is Landing
10998
+ * 2's lesson and the reason `unmanagedArrayElementSize` exists.
10999
+ */
11000
+ private compileDynamicArrayElementAddress;
11001
+ /**
11002
+ * The bounds check an `@unmanaged @dynamic [T * n]` access performs before it
11003
+ * touches memory: `if ((u32)index >= (u32)length) trap`.
11004
+ *
11005
+ * **Unsigned** on purpose. A negative index is a huge `u32`, so it fails the
11006
+ * same comparison as any other out-of-range value — one check covers both ends
11007
+ * without a second comparison, and `y[-1]` cannot walk backwards out of the
11008
+ * buffer.
11009
+ *
11010
+ * The length comes from the header directly rather than from the `n` binding.
11011
+ * `n` is a snapshot taken at entry (D5 of 4a); the header is what the buffer
11012
+ * currently says, and a bounds check is about the memory that exists now.
11013
+ *
11014
+ * The message is a literal: `makeAbort` takes a string expression, and nothing
11015
+ * in this compiler formats an index and a length into one (measured — 4a's
11016
+ * `changetype` trap is the same shape). Naming the two causes would mean
11017
+ * building a string at every check.
11018
+ */
11019
+ private compileDynamicArrayBoundsCheck;
11020
+ /**
11021
+ * Compiles the byte address of `y[i]` for an unmanaged array: `base + i*sizeof(T)`.
11022
+ *
11023
+ * Shared by the read and the write arm, so the two cannot disagree about which
11024
+ * bytes they address — the same reason `compileFixedArrayElementAccess` and
11025
+ * `compileFixedArrayElementAddress` are split.
11026
+ *
11027
+ * The stride is a **multiply**, not the fixed-array arm's `1 << alignof(T)`.
11028
+ * That shift is right only for the elements the fixed arm admits (records are
11029
+ * refused there, so every element is a scalar, where size and alignment
11030
+ * coincide). A record element is the ordinary case here, and for a record the
11031
+ * two disagree: two `f64`s give `sizeof` 16 and `alignof` 8.
11032
+ */
11033
+ private compileUnmanagedArrayElementAddress;
11034
+ /**
11035
+ * Compiles `y[i]` for an unmanaged array, `[T *]` — both directions.
11036
+ *
11037
+ * Two things separate this from {@link compileFixedArrayElementAccess}:
11038
+ *
11039
+ * - **The stride is a multiply** (see the address helper).
11040
+ *
11041
+ * - **A record element compiles to an address, not a load.** A record's
11042
+ * `toRef` is the 4-byte reference, so loading there would read the record's
11043
+ * first four bytes and call them a pointer. So the address is yielded typed
11044
+ * as the element, and `.field` on it resolves through the record's own
11045
+ * accessor — a load or a store at `addr + offsetof`. **That is also how a
11046
+ * field write works**: `y[i].x = v` reaches the property setter, whose
11047
+ * `this` is this address. No separate arm.
11048
+ *
11049
+ * Reads are unchecked. `*` says the length is unknown, so there is nothing to
11050
+ * check against; `@dynamic` is where a checked form lives.
11051
+ *
11052
+ * A whole-record **write** is refused, keyed on the element being a record.
11053
+ * The key is the element kind, not "both sides have the same type": a scalar
11054
+ * element assignment (`a[i] = 5` over `[u32 *]`) is an ordinary store and must
11055
+ * keep compiling. Refusing by type equality would have caught that case too,
11056
+ * which is the trap this comment exists to keep shut.
11057
+ */
11058
+ private compileUnmanagedArrayElementAccess;
11059
+ /**
11060
+ * Compiles `y[i]` for an `@unmanaged @dynamic [T * n]` — both directions.
11061
+ *
11062
+ * Differs from {@link compileUnmanagedArrayElementAccess} in exactly one place,
11063
+ * and it is the whole point of the kind: **the bounds check**. A `[T *]` has no
11064
+ * length to check against, so its access is unchecked by contract; a dynamic
11065
+ * array carries one in its header, and an index outside it is a diagnostic
11066
+ * rather than whatever memory happens to follow.
11067
+ *
11068
+ * Everything else is the unmanaged arm's shape, deliberately:
11069
+ *
11070
+ * - a **record element** compiles to the address, typed as the element, so
11071
+ * `.field` reads and writes go through the record's own accessor — which is
11072
+ * also how `y[i].field = v` works, with no arm of its own;
11073
+ * - a **scalar element** is loaded or stored at its own width;
11074
+ * - a whole-record **write** is refused, keyed on the element being a record
11075
+ * rather than on type equality, so `a[i] = 5` over `[u32 * n]` still
11076
+ * compiles.
11077
+ *
11078
+ * Compound assignment and increment call this **twice** — once by way of the
11079
+ * read the operator performs, once for the store — so the check is emitted
11080
+ * twice. That is the smaller of the two correct shapes: sharing one check
11081
+ * would mean threading the checked address through the operator's own codegen,
11082
+ * which does not know this arm exists.
11083
+ */
11084
+ private compileDynamicArrayElementAccess;
10462
11085
  /**
10463
11086
  * Compiles `array[index]` for a `T[N]` inline array: read and write.
10464
11087
  *
@@ -10599,6 +11222,42 @@ declare module "types:tensionscript/src/compiler" {
10599
11222
  addDebugLocation(expr: ExpressionRef, range: Range): void;
10600
11223
  /** Checks whether a particular function signature is supported. */
10601
11224
  checkSignatureSupported(signature: Signature, reportNode: FunctionTypeNode): boolean;
11225
+ /**
11226
+ * The `[T * N]` half of signature support, kept separate so that a refusal
11227
+ * can **stop** the function rather than merely be reported.
11228
+ *
11229
+ * `checkSignatureSupported` reports and compilation carries on — the right
11230
+ * shape for a feature gate, whose codegen path can still produce a module.
11231
+ * Not for this one: a value-form `[T * N]` parameter or return has no slot, so
11232
+ * continuing reaches `Signature#paramRefs` / `#resultRefs`, which call
11233
+ * `toRef`, which asserts for the value form by design. Measured before this
11234
+ * split: `export function f(a: [u32 * 4]): u32` and
11235
+ * `export function f(p: usize): [u32 * 4]` both crashed the compiler inside
11236
+ * `toRef` instead of reporting.
11237
+ */
11238
+ private checkFixedArrayStorageInSignature;
11239
+ /**
11240
+ * Refuses a `[T * N]` that has no storage in the position it was written in.
11241
+ *
11242
+ * Landing 5a.5 made the memory model part of the Type, which splits the kind
11243
+ * into three: the **view** (`@unmanaged [T * N]`, one address — works
11244
+ * everywhere `@unmanaged T` works), the **heap** form (`@managed [T * N]`,
11245
+ * Landing 5c) and the **value** form (`[T * N]`, `u32[N]`, `@inline [T * N]`
11246
+ * — N inline elements, no address of its own).
11247
+ *
11248
+ * The value form exists and is laid out correctly: it is a record field's
11249
+ * type, `sizeof` counts N × sizeof(T), and Landing 1's fixtures assert all of
11250
+ * that. What it does not have is a **slot**. `toRef` asserts for it by design,
11251
+ * so every position that needs a WASM local, parameter or result must be
11252
+ * refused *before* that assert is reached — otherwise the compiler crashes
11253
+ * where the answer is a diagnostic. Landing 5b gives the form inline storage
11254
+ * (stack slot, copy at the ABI, sret) and these refusals come out.
11255
+ *
11256
+ * This mirrors `Program.checkInlineStructValue`, which refuses the same class
11257
+ * of position for the same reason (a value with no address where one is
11258
+ * needed). `position` names the site so the message says which case was hit.
11259
+ */
11260
+ private checkFixedArrayStoragePosition;
10602
11261
  /** Is `type` an `@inline struct`: a value carried as its fields, never as a
10603
11262
  * pointer? */
10604
11263
  /**
@@ -10653,6 +11312,23 @@ declare module "types:tensionscript/src/compiler" {
10653
11312
  * there is no address to pass. Anything else refuses by name rather than
10654
11313
  * compiling a pointer.
10655
11314
  */
11315
+ /**
11316
+ * Expands a value-form `[T * N]` argument into one operand per element leaf.
11317
+ *
11318
+ * Two shapes reach here, and they differ in where the leaves come from:
11319
+ *
11320
+ * - **A value-form local** — push its SROA leaves. `pushSroaLeafOperands`
11321
+ * walks the map depth first through placeholders, which is exactly the
11322
+ * element-major order `paramRefs` declares.
11323
+ * - **An array literal** — compile each element into its own operand. A record
11324
+ * element is several leaves and no literal names them (`{x, y}` goes through
11325
+ * the class path and *allocates*), so it is refused by name, matching
11326
+ * `fillSroaArrayFromLiteral`.
11327
+ *
11328
+ * Anything else — a call returning an array, a conditional — would need the
11329
+ * value materialised somewhere, which is the return path, not this one.
11330
+ */
11331
+ private expandSroaArrayArgument;
10656
11332
  private expandSroaArgument;
10657
11333
  /**
10658
11334
  * The SROA local an expression names, or `null`.
@@ -10939,6 +11615,165 @@ declare module "types:tensionscript/src/compiler" {
10939
11615
  * has already reported why), and the caller has been told.
10940
11616
  */
10941
11617
  emitSroaStores(type: Type, fieldLocals: Map<string, Local>, dest: ExpressionRef, baseOffset: number, stmts: ExpressionRef[]): void;
11618
+ /**
11619
+ * Whether `type` is a fixed array in its **value** form — N inline elements,
11620
+ * no address of its own — as opposed to the `@unmanaged` view (a pointer,
11621
+ * Landing 5a.5) or the `@managed` heap form (Landing 5c).
11622
+ */
11623
+ isValueFormFixedArray(type: Type): boolean;
11624
+ /**
11625
+ * The largest `N` whose value-form array can be indexed with a **runtime**
11626
+ * index.
11627
+ *
11628
+ * A runtime index on SROA leaves has no address to scale, so the element is
11629
+ * selected with a `br_table` over one block per element. Past a handful of
11630
+ * blocks that dispatch costs more than the thing it replaces: the caller who
11631
+ * wants an arbitrary index over many elements wants a pointer, and
11632
+ * `@unmanaged [T * N]` gives them one with no block at all. Eight is the
11633
+ * boundary between the two. Landing 5d's spill strategy is what would lift it.
11634
+ */
11635
+ private static readonly VALUE_ARRAY_MAX_RUNTIME_INDEX;
11636
+ /** Per-emission counter for the runtime-index block labels, so two accesses in
11637
+ * one function cannot collide (Binaryen requires unique block names). */
11638
+ private sroaSelectId;
11639
+ /**
11640
+ * Fills `into` with one entry per **element** of a value-form `[T * N]`.
11641
+ *
11642
+ * The same shape `fillSroaFieldLocals` gives a record, with the index where
11643
+ * the field name would be: the map is string-keyed and `"0"`, `"1"` are
11644
+ * ordinary keys, so no new container is needed — only a new way to enumerate
11645
+ * the entries.
11646
+ *
11647
+ * - A **scalar** element gets one WASM local, named `<path>$<index>`.
11648
+ * - A **record** element is several leaves, so it gets the placeholder local
11649
+ * (`index -1`) carrying a nested map — exactly what a nested `@inline`
11650
+ * field gets, and for the same reason: `arr[i]` on a record names a whole
11651
+ * sub-value that no single local can hold, while `arr[i].x` descends one
11652
+ * hop to a leaf.
11653
+ *
11654
+ * Returns `false` when an element was refused, having reported why.
11655
+ */
11656
+ private fillSroaArrayLocals;
11657
+ /**
11658
+ * Compiles a value-form `[T * N]` local as SROA: one WASM local per element
11659
+ * leaf, no local for the array itself, no store for the zero case.
11660
+ *
11661
+ * Returns `true` when the declaration was handled here (the caller must not
11662
+ * fall through to the ordinary local path).
11663
+ */
11664
+ private compileSroaValueArrayLocal;
11665
+ /**
11666
+ * Stores an array literal's elements into a value-form array's leaves.
11667
+ *
11668
+ * A scalar element is compiled straight into its leaf. A **record** element is
11669
+ * several leaves and a literal cannot name them one at a time, so it is
11670
+ * refused by name here rather than compiled: the object-literal spelling
11671
+ * (`{ x: 1.5, y: 2.5 }`) goes through the class path and *allocates* — measured,
11672
+ * `const p: P = { x: 1.5 }` emitted `P#constructor` plus setters and needed
11673
+ * `env.abort`, i.e. a heap record behind a pointer, which is not a value.
11674
+ * Initialising the elements field by field (`arr[0].x = 10.5`) is the working
11675
+ * route, and it is what the diagnostic says.
11676
+ */
11677
+ private fillSroaArrayFromLiteral;
11678
+ /**
11679
+ * The local behind `name`, when that local is a value-form `[T * N]` — i.e. a
11680
+ * value stored as one WASM local per element leaf, with no slot of its own.
11681
+ *
11682
+ * Returns `null` for everything else, including the view form (which *is* an
11683
+ * address and goes through the ordinary path).
11684
+ */
11685
+ private resolveSroaValueArray;
11686
+ /**
11687
+ * The compile-time value of an index expression, or `-1` when it is not a
11688
+ * non-negative integer literal.
11689
+ *
11690
+ * `-1` is the "not constant" answer rather than an error: a runtime index is a
11691
+ * legitimate access, it just takes the `br_table` path.
11692
+ */
11693
+ private constantIndexValue;
11694
+ /**
11695
+ * Indexes a value-form `[T * N]` local, whose elements are separate WASM
11696
+ * locals.
11697
+ *
11698
+ * A **constant** index needs no dispatch at all: it names one leaf, and the
11699
+ * access is `local.get` / `local.set` on it. A **runtime** index has no address
11700
+ * to scale, so it is selected with a `br_table` over one block per element —
11701
+ * bounded by {@link VALUE_ARRAY_MAX_RUNTIME_INDEX}, past which the caller wants
11702
+ * a pointer view instead.
11703
+ */
11704
+ private compileSroaValueArrayElementAccess;
11705
+ /**
11706
+ * Selects one element leaf of a value-form array by a runtime index.
11707
+ *
11708
+ * The shape is one `br_table` with a block per element, each block yielding
11709
+ * that element's leaf — the only way to choose between WASM locals at runtime,
11710
+ * since they are not addressable. An index outside `[0, N)` is the caller's
11711
+ * error, exactly as it is for `[T *]`: the extent is compile-time and there is
11712
+ * no length on the value stack to check against.
11713
+ */
11714
+ private compileSroaValueArrayRuntimeIndex;
11715
+ /**
11716
+ * Reads `arr[i].field` where `arr` is a value-form array **of records** and `i`
11717
+ * is a runtime index. Returns `0` when the shape does not apply.
11718
+ *
11719
+ * A record element is several leaves per candidate, so a single-value
11720
+ * `br_table` cannot select one — but a *field* is one leaf per candidate, so
11721
+ * the selection is per leaf: one block per element, each yielding that
11722
+ * element's `<field>` leaf. Same block nesting as the scalar runtime index
11723
+ * (`compileSroaValueArrayRuntimeIndex`), one hop lower.
11724
+ *
11725
+ * This is the shape the collision solver writes by hand today —
11726
+ * `arr[i].x` with a runtime `i` over a run of records — which is why it is
11727
+ * worth the dispatch rather than only supporting a constant index.
11728
+ */
11729
+ private compileSroaArrayLeafByRuntimeIndex;
11730
+ /**
11731
+ * Writes `arr[i].field = value` where `arr` is a value-form array of records
11732
+ * and `i` is a runtime index. Returns `0` when the shape does not apply.
11733
+ *
11734
+ * The mirror of {@link compileSroaArrayLeafByRuntimeIndex}: one block per
11735
+ * element, each storing into that element's `<field>` leaf. The value is
11736
+ * materialised into a temp first — one WASM node cannot be the parent of N
11737
+ * stores — which is the same reason the scalar runtime-index write does it.
11738
+ */
11739
+ private compileSroaArrayLeafStoreByRuntimeIndex;
11740
+ /**
11741
+ * Emits the `br_table` that selects one of `leafIndices` by a runtime index.
11742
+ *
11743
+ * The only way to choose between WASM locals at runtime, since locals are not
11744
+ * addressable: one block per candidate, the body sitting **after** its block's
11745
+ * `end` (where a branch target lands), element 0 as the fall-through.
11746
+ *
11747
+ * One helper rather than four. After Landing 5b.1 four call sites emitted this
11748
+ * same nesting — scalar read, scalar write, record-field read, record-field
11749
+ * write — differing only in which leaf each block touched. Params and returns
11750
+ * would have added more, and a structural bug in one of them would have been
11751
+ * invisible in the other three.
11752
+ *
11753
+ * **A method, not a closure.** The self-hosted compiler refuses an arrow
11754
+ * function that captures a local without a declared type — `AS100: a captured
11755
+ * local without a declared type (closures lowering)` — so the four sites
11756
+ * cannot share a callback.
11757
+ *
11758
+ * `storeValueLocal` is `-1` for a read; for a store it names the temp holding
11759
+ * the value. A store's body cannot reuse the caller's expression tree: a WASM
11760
+ * node has one parent, so N stores cannot share one subtree. `resultType` is
11761
+ * `TypeRef.None` for a store, in which case the dispatch yields nothing.
11762
+ */
11763
+ private compileLeafIndexSelect;
11764
+ /**
11765
+ * Returns a value-form `[T * N]` value as WASM multivalue results.
11766
+ *
11767
+ * One result per element leaf, element-major — the order `resultRefs` declares
11768
+ * and the caller's unpack indexes into. The leaves are read out of the value's
11769
+ * SROA locals; a fresh array literal goes through the same path, because the
11770
+ * literal binds to leaves before the return is compiled.
11771
+ *
11772
+ * Requires `Feature.MultiValue`, which `Resolver` turns on when it resolves
11773
+ * this return type (`enableMultiValueOnce`), so a module that returns one of
11774
+ * these always has the feature and a module that does not never does.
11775
+ */
11776
+ private compileMultivalueReturnStatement;
10942
11777
  private compileSroaLocal;
10943
11778
  /**
10944
11779
  * The instance fields of an `@inline struct`, in declaration order, or `null`