@geastack/compiler 1.0.15 → 1.0.17

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.
Files changed (61) hide show
  1. package/dist/conversion/nodes.d.ts +31 -0
  2. package/dist/conversion/nodes.js +46 -2
  3. package/dist/conversion/record-view.d.ts +24 -0
  4. package/dist/conversion/record-view.js +54 -0
  5. package/dist/ir/certify.js +23 -1
  6. package/dist/ir/integers.js +24 -2
  7. package/dist/ir/lower-operands.d.ts +10 -1
  8. package/dist/ir/lower-operands.js +58 -2
  9. package/dist/ir/lower.js +12 -3
  10. package/dist/ir/reflection-demand.js +5 -0
  11. package/dist/ir/transfer.js +17 -8
  12. package/dist/plugins/apple/host.d.ts +2 -0
  13. package/dist/plugins/apple/host.js +13 -6
  14. package/dist/plugins/gea/host.d.ts +2 -0
  15. package/dist/plugins/gea/host.js +11 -4
  16. package/dist/plugins/host-package.d.ts +37 -0
  17. package/dist/plugins/host-package.js +28 -0
  18. package/dist/plugins/load.js +15 -0
  19. package/dist/plugins/webgl/host.d.ts +9 -3
  20. package/dist/plugins/webgl/host.js +15 -6
  21. package/dist/plugins/webgl/plugin.js +4 -1
  22. package/dist/representation/embedded-carriers.d.ts +22 -0
  23. package/dist/representation/embedded-carriers.js +105 -0
  24. package/dist/semantics/frontend.js +4 -1
  25. package/dist/semantics/model/operands.d.ts +17 -0
  26. package/dist/semantics/model/operations.d.ts +28 -1
  27. package/dist/semantics/normalize/flow/callable-reach.js +1 -3
  28. package/dist/semantics/normalize/global-host-mutations.js +2 -2
  29. package/dist/semantics/normalize/producers/allocations.js +2 -0
  30. package/dist/semantics/normalize/producers/bindings.js +5 -1
  31. package/dist/semantics/normalize/producers/boundary.d.ts +1 -0
  32. package/dist/semantics/normalize/producers/boundary.js +6 -2
  33. package/dist/semantics/normalize/producers/class-lifecycle.js +3 -0
  34. package/dist/semantics/normalize/producers/control.js +2 -2
  35. package/dist/semantics/normalize/producers/erasure.d.ts +7 -0
  36. package/dist/semantics/normalize/producers/erasure.js +19 -0
  37. package/dist/semantics/normalize/producers/exact-arms.d.ts +12 -0
  38. package/dist/semantics/normalize/producers/exact-arms.js +12 -0
  39. package/dist/semantics/normalize/producers/invocations.js +33 -1
  40. package/dist/semantics/normalize/reachability.d.ts +20 -5
  41. package/dist/semantics/normalize/reachability.js +119 -14
  42. package/dist/semantics/normalize/specialization.js +1 -4
  43. package/dist/targets/cpp/class-layout.d.ts +40 -0
  44. package/dist/targets/cpp/class-layout.js +112 -0
  45. package/dist/targets/cpp/class-properties/emit-class-properties.js +4 -4
  46. package/dist/targets/cpp/conversions.js +63 -1
  47. package/dist/targets/cpp/emit-callable.js +7 -6
  48. package/dist/targets/cpp/emit-context.d.ts +38 -1
  49. package/dist/targets/cpp/emit-context.js +28 -2
  50. package/dist/targets/cpp/emit-narrowing.d.ts +1 -1
  51. package/dist/targets/cpp/emit-narrowing.js +54 -1
  52. package/dist/targets/cpp/emit-properties.js +13 -3
  53. package/dist/targets/cpp/emit-record-view.js +31 -0
  54. package/dist/targets/cpp/emit.d.ts +3 -3
  55. package/dist/targets/cpp/emit.js +11 -5
  56. package/dist/targets/cpp/prototype/emit-prototype-regexp.d.ts +1 -1
  57. package/dist/targets/cpp/prototype/emit-prototype-regexp.js +58 -2
  58. package/dist/targets/cpp/translation-unit.js +32 -16
  59. package/package.json +5 -5
  60. package/src/targets/cpp/runtime/gea_dynamic_proxy.h +38 -0
  61. package/src/targets/cpp/runtime/gea_runtime.h +247 -45
@@ -8,6 +8,7 @@ import { implementationSignatureOf } from '../structural-declarations.js';
8
8
  import { physicalGeneratorOverloadResultAt } from '../physical-overload-result.js';
9
9
  import { regionId, semanticResultId } from '../../../identity/ids.js';
10
10
  import { bindingKindOf } from './binding-kind.js';
11
+ import { declaresExactArms } from './exact-arms.js';
11
12
  import { blocked, mintOperationId, mintResult, operand } from './mint.js';
12
13
  import { calleeAwareTypeAt, objectDescriptorReturnTypeAt, resolvedCalleeSignatureType, sourceForValue, unwrapErased, valueEdgesInto } from './shared.js';
13
14
  import { isShortCircuitingCall, optionalChainGuardOf, optionalCallGuardOf, presentReturnTypeOf } from './optional-chain.js';
@@ -152,6 +153,34 @@ const argumentConversionRolesOf = (selected, operands) => {
152
153
  }
153
154
  return roles;
154
155
  };
156
+ /**
157
+ * The `exact-arm` roles of a call into an `@gea-exact-arms` implementation
158
+ * (`ConversionRoleTarget.owner`): each argument's type under the overload the
159
+ * checker resolved. Published beside the implementation's own
160
+ * `parameter-slot` roles rather than instead of them, because the slot is
161
+ * still the implementation's union -- this only says which arm of it the
162
+ * argument enters. Nothing for a call that resolved straight to the body
163
+ * (no overload was chosen, so there is no arm to name) or to a generic
164
+ * overload (the instantiated parameter types are not public, exactly as
165
+ * `argumentConversionRolesOf` refuses them).
166
+ */
167
+ const exactArmArgumentRolesOf = (context, node, resolved, implementation, operands) => {
168
+ if (!resolved || !implementation?.declaration || !declaresExactArms(implementation.declaration))
169
+ return [];
170
+ const selected = buildSelectedSignature(context, node, resolved);
171
+ if (!selected || selected.typeArguments.kind !== 'none')
172
+ return [];
173
+ const roles = [];
174
+ for (const argument of operands) {
175
+ if (argument.role !== 'argument')
176
+ continue;
177
+ const parameter = selected.parameters[argument.ordinal];
178
+ if (!parameter || parameter.rest)
179
+ continue;
180
+ roles.push({ role: 'argument', ordinal: argument.ordinal, owner: 'exact-arm', type: parameter.slot });
181
+ }
182
+ return roles;
183
+ };
155
184
  const buildSelectedSignature = (context, node, signature) => {
156
185
  const declaration = signature.declaration;
157
186
  // No declaration node (the implicit constructor of a class with no written
@@ -1740,7 +1769,10 @@ export const createInvocationProducer = (context) => ({
1740
1769
  ? { intrinsicReflection: intrinsicPropertyCall }
1741
1770
  : {}),
1742
1771
  operands,
1743
- conversionRoles: argumentConversionRolesOf(selectedSignature, operands),
1772
+ conversionRoles: [
1773
+ ...argumentConversionRolesOf(selectedSignature, operands),
1774
+ ...exactArmArgumentRolesOf(context, node, resolvedSignature, implementationSignature, operands)
1775
+ ],
1744
1776
  // Two results, and they are different values. `value` is what the call
1745
1777
  // returned, which exists only on the branch where the guard was present;
1746
1778
  // `short-circuit` is what the *expression* evaluates to, which is that
@@ -37,17 +37,16 @@ import ts from 'typescript';
37
37
  * - A **function declaration**. Its evaluation is `InstantiateFunctionObject`:
38
38
  * a closure is created and bound. Nothing observable happens, and the body
39
39
  * does not run.
40
- * - A **class declaration** with no decorators, no `extends` clause, no static
40
+ * - A **class declaration** with no decorators, no static
41
41
  * block, no static field initializer and no computed member name.
42
42
  * `ClassDefinitionEvaluation` for such a class evaluates nothing at all, and
43
43
  * this compiler lowers class evaluation to *no runtime step at all*
44
44
  * (`ir/lower.ts`, `case 'class-lifecycle'`): the struct and the construct
45
45
  * function it emits already are the layout the definition events record. A
46
46
  * static block or a static initializer is the opposite -- each is a real
47
- * region with a body -- so a class carrying one is kept; so is a class with
48
- * a heritage clause, whose one definition-time step (the heritage read) is
49
- * the event the projection takes the struct's base from (see
50
- * `classDefinitionIsInert`).
47
+ * region with a body -- so a class carrying one is kept. An `extends`
48
+ * clause is tolerated only in the two spellings `heritageIsInert` proves
49
+ * evaluate nothing; any other base keeps the class.
51
50
  * - A **variable statement** whose every declarator binds a plain name and has
52
51
  * either no initializer or an initializer drawn from `initializerIsInert`'s
53
52
  * whitelist: literals, function/arrow/class expressions, and object/array
@@ -185,5 +184,21 @@ export interface ReachabilityInput {
185
184
  */
186
185
  readonly hostReachedMemberKeys?: ReadonlySet<string>;
187
186
  }
187
+ /**
188
+ * Whether the statements this program REACHES in `file` evaluate anything.
189
+ *
190
+ * `evaluatesNothing` above is the syntactic answer for a whole file;
191
+ * this is the same question after pruning, and it is the one the entry needs:
192
+ * a module body is called because something in it runs. A class retained for
193
+ * its LAYOUT alone is the case that separates them. It is a live statement --
194
+ * it publishes a class-lifecycle operation, so the census gives its file a
195
+ * module-body region -- and by `layoutClasses`' own definition it runs no
196
+ * constructor, no initializer and no module effect, so lowering gives that
197
+ * region no body. A rooted script whose every class is pruned except one some
198
+ * annotation names (node-compat's `whatwg-streams.ts`, in a program that
199
+ * touches no stream) was scheduled on the strength of the region and refused
200
+ * by the printer for want of the body.
201
+ */
202
+ export declare const fileEvaluates: (reachable: ProgramReachability, file: ts.SourceFile) => boolean;
188
203
  export declare const moduleEvaluationOrder: (input: ReachabilityInput) => readonly ts.SourceFile[];
189
204
  export declare const censusReachability: (input: ReachabilityInput) => ProgramReachability;
@@ -92,21 +92,63 @@ const computedKeyIsInert = (checker, name) => {
92
92
  const declarations = checker.getSymbolAtLocation(root)?.declarations ?? [];
93
93
  return declarations.length > 0 && declarations.every((declaration) => declaration.getSourceFile().isDeclarationFile);
94
94
  };
95
+ /**
96
+ * Whether evaluating a class's `extends` clause is provably not an action.
97
+ *
98
+ * `ClassDefinitionEvaluation` (15.7.14) evaluates the heritage expression and
99
+ * requires a constructor. Two spellings cannot do anything else. A bare
100
+ * identifier naming a class DECLARED EARLIER at the top level of the same file
101
+ * is a read of an initialized binding -- past its temporal dead zone, never a
102
+ * getter, always a constructor. And a bare identifier whose every declaration
103
+ * is ambient names the language's own intrinsic (`Error`), for the reason
104
+ * `computedKeyIsInert` gives. Everything else is kept: a call (`mixin(Base)`),
105
+ * a property read (`ns.Base`, which can be a getter), a base declared later
106
+ * (a TDZ throw), and an imported base, whose module a cycle may not have
107
+ * evaluated yet.
108
+ *
109
+ * This used to answer `false` for every `extends`, for a reason that was never
110
+ * about evaluation: `projection/classes.ts` takes a struct's base from the
111
+ * heritage event a LIVE class publishes, so pruning a subclass some type still
112
+ * named left `struct One final {}` with no base under an upcast. That is a
113
+ * question about who names the class, and `markTypeNamedClasses` now answers
114
+ * it where it belongs -- a class any live annotation, alias or interface names
115
+ * is opened for its layout and publishes that event. What is pruned here is a
116
+ * subclass NOTHING names, whose struct nothing emits.
117
+ *
118
+ * The blanket rule's cost was every unused hierarchy in a rooted script:
119
+ * node-compat's `whatwg-streams.ts` and `abort-events.ts` are roots of every
120
+ * program, so every program carried `ReadableByteStreamController`,
121
+ * `TextEncoderStream`, `DOMException` and, through them, the whole stream
122
+ * implementation -- ~2.4k emitted lines in a program that prints one number.
123
+ */
124
+ const heritageIsInert = (checker, node) => {
125
+ for (const clause of node.heritageClauses ?? []) {
126
+ if (clause.token !== ts.SyntaxKind.ExtendsKeyword)
127
+ continue;
128
+ for (const type of clause.types) {
129
+ if (!ts.isIdentifier(type.expression))
130
+ return false;
131
+ const declarations = checker.getSymbolAtLocation(type.expression)?.declarations ?? [];
132
+ if (declarations.length === 0)
133
+ return false;
134
+ if (declarations.every((declaration) => declaration.getSourceFile().isDeclarationFile))
135
+ continue;
136
+ const file = node.getSourceFile();
137
+ const settled = declarations.every((declaration) => ts.isClassDeclaration(declaration) &&
138
+ declaration.getSourceFile() === file &&
139
+ declaration.parent === file &&
140
+ declaration.end <= node.pos);
141
+ if (!settled)
142
+ return false;
143
+ }
144
+ }
145
+ return true;
146
+ };
95
147
  /** Whether a class's *definition* evaluates anything beyond binding its own name. */
96
148
  const classDefinitionIsInert = (checker, node) => {
97
149
  if (ts.canHaveDecorators(node) && (ts.getDecorators(node)?.length ?? 0) > 0)
98
150
  return false;
99
- // The one step `ClassDefinitionEvaluation` performs for a decorator-free
100
- // class is its heritage read, and that step is not inert in THIS compiler
101
- // even though it calls nothing: `projection/classes.ts` reads a class's
102
- // `base` link off the class-lifecycle heritage event the census publishes
103
- // for the definition, and `records.ts` spells `struct D : B` from that
104
- // link alone -- while the struct itself is emitted whenever the class's
105
- // shape is reachable, which a type position keeps it. Pruning an
106
- // un-instantiated `class One extends Base` whose type still names a union
107
- // arm emitted `struct One final {}` with no base, and the upcast
108
- // `Ref<Base>(Ref<One>)` the union's recast renders stopped compiling.
109
- if (node.heritageClauses?.some((clause) => clause.token === ts.SyntaxKind.ExtendsKeyword))
151
+ if (!heritageIsInert(checker, node))
110
152
  return false;
111
153
  return node.members.every((member) => {
112
154
  if (ts.canHaveDecorators(member) && (ts.getDecorators(member)?.length ?? 0) > 0)
@@ -238,6 +280,31 @@ const objectMemberIsInert = (checker, member) => {
238
280
  * module (TS2448, which this compiler refuses the program for), it does not
239
281
  * across an import cycle.
240
282
  */
283
+ /**
284
+ * `Symbol()` / `Symbol('description')` on the language's own `Symbol`.
285
+ *
286
+ * ECMA-262 20.4.1.1 does one thing a program could observe: `ToString` of the
287
+ * description, and `ToString` of a string literal (or of nothing) runs no
288
+ * code. What is left is minting a symbol, and a symbol bound to a name nothing
289
+ * reads is not observable -- which is the only case this is ever asked about,
290
+ * because a binding something names is opened regardless. The callee must be
291
+ * the ambient intrinsic for the reason `computedKeyIsInert` gives: a user
292
+ * `Symbol` is an ordinary call.
293
+ *
294
+ * A module-private brand token is what reaches it: node-compat's
295
+ * `abort-events.ts` is a root of every program and declares
296
+ * `const abortSignalConstructionToken = Symbol('AbortSignal construction')`,
297
+ * so every program kept a module body whose whole content was that call.
298
+ */
299
+ const intrinsicSymbolCallIsInert = (checker, node) => {
300
+ if (!ts.isIdentifier(node.expression) || node.expression.text !== 'Symbol' || node.arguments.length > 1)
301
+ return false;
302
+ const description = node.arguments[0];
303
+ if (description !== undefined && !ts.isStringLiteral(description) && !ts.isNoSubstitutionTemplateLiteral(description))
304
+ return false;
305
+ const declarations = checker.getSymbolAtLocation(node.expression)?.declarations ?? [];
306
+ return declarations.length > 0 && declarations.every((declaration) => declaration.getSourceFile().isDeclarationFile);
307
+ };
241
308
  const initializerIsInert = (checker, node) => {
242
309
  if (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node))
243
310
  return initializerIsInert(checker, node.expression);
@@ -286,7 +353,7 @@ const initializerIsInert = (checker, node) => {
286
353
  declarations.every((declaration) => ts.isClassDeclaration(declaration) && declaration.getSourceFile() === node.getSourceFile()));
287
354
  }
288
355
  if (ts.isCallExpression(node))
289
- return closureFactoryCallIsInert(checker, node);
356
+ return intrinsicSymbolCallIsInert(checker, node) || closureFactoryCallIsInert(checker, node);
290
357
  // `-1` and `+1` are how a negative numeric constant is spelled; nothing else
291
358
  // unary is admitted, because every other operand form can reach user code.
292
359
  if (ts.isPrefixUnaryExpression(node)) {
@@ -547,9 +614,33 @@ const evaluatesNothing = (file) => file.statements.every((statement) => {
547
614
  return true;
548
615
  if (statement.moduleSpecifier !== undefined)
549
616
  return false;
550
- return statement.exportClause !== undefined && ts.isNamedExports(statement.exportClause) && statement.exportClause.elements.length === 0;
617
+ return (statement.exportClause !== undefined && ts.isNamedExports(statement.exportClause) && statement.exportClause.elements.length === 0);
551
618
  }
552
- return ts.canHaveModifiers(statement) && (ts.getModifiers(statement) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword);
619
+ return (ts.canHaveModifiers(statement) &&
620
+ (ts.getModifiers(statement) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword));
621
+ });
622
+ /**
623
+ * Whether the statements this program REACHES in `file` evaluate anything.
624
+ *
625
+ * `evaluatesNothing` above is the syntactic answer for a whole file;
626
+ * this is the same question after pruning, and it is the one the entry needs:
627
+ * a module body is called because something in it runs. A class retained for
628
+ * its LAYOUT alone is the case that separates them. It is a live statement --
629
+ * it publishes a class-lifecycle operation, so the census gives its file a
630
+ * module-body region -- and by `layoutClasses`' own definition it runs no
631
+ * constructor, no initializer and no module effect, so lowering gives that
632
+ * region no body. A rooted script whose every class is pruned except one some
633
+ * annotation names (node-compat's `whatwg-streams.ts`, in a program that
634
+ * touches no stream) was scheduled on the strength of the region and refused
635
+ * by the printer for want of the body.
636
+ */
637
+ export const fileEvaluates = (reachable, file) => reachable.statementsOf(file).some((statement) => {
638
+ if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement))
639
+ return false;
640
+ if (ts.isClassDeclaration(statement) && reachable.classIsLayoutOnly(statement))
641
+ return false;
642
+ return !(ts.canHaveModifiers(statement) &&
643
+ (ts.getModifiers(statement) ?? []).some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword));
553
644
  });
554
645
  export const moduleEvaluationOrder = (input) => {
555
646
  const compiled = new Set(input.files);
@@ -787,11 +878,25 @@ export const censusReachability = (input) => {
787
878
  * header already gives, and layout-only is the smallest promotion there is:
788
879
  * no module evaluation, no constructor, no member body.
789
880
  */
881
+ // A type alias or interface is a NAME for the types inside it, so a class it
882
+ // names is named by whatever names the alias: `type Arm = One | Two` then
883
+ // `let v: Arm` types a live cell as `One`. The walk never enters a type
884
+ // declaration on its own (`markReferences` returns on both), so it is entered
885
+ // here, once, from the reference that makes it matter. This is what lets
886
+ // `heritageIsInert` prune an unnamed subclass without losing a named one.
887
+ const followedTypeDeclarations = new Set();
790
888
  const markTypeNamedClasses = (node) => {
791
889
  const named = ts.isTypeReferenceNode(node) ? node.typeName : ts.isExpressionWithTypeArguments(node) ? node.expression : null;
792
890
  if (named !== null) {
793
891
  const symbol = input.checker.getSymbolAtLocation(named);
794
892
  for (const declaration of symbol ? declarationsOf(input.checker, symbol) : []) {
893
+ if (ts.isTypeAliasDeclaration(declaration) || ts.isInterfaceDeclaration(declaration)) {
894
+ if (followedTypeDeclarations.has(declaration))
895
+ continue;
896
+ followedTypeDeclarations.add(declaration);
897
+ ts.forEachChild(declaration, markTypeNamedClasses);
898
+ continue;
899
+ }
795
900
  if (!ts.isClassDeclaration(declaration))
796
901
  continue;
797
902
  const file = declaration.getSourceFile();
@@ -1781,10 +1781,7 @@ export const censusSpecializations = (checker, sourceFiles, reachable, namespace
1781
1781
  // refusal it replaces. Those copies are two layouts and split.
1782
1782
  const stored = parametersInInstanceStorage(checker, declaration);
1783
1783
  const foldable = (candidate) => ![...layoutRelevant].some((index) => stored.has(index) &&
1784
- fillings.some((other, at) => minted[at] === true &&
1785
- other[index] !== candidate[index] &&
1786
- !openFilling(other[index]) &&
1787
- !openFilling(candidate[index])));
1784
+ fillings.some((other, at) => minted[at] === true && other[index] !== candidate[index] && !openFilling(other[index]) && !openFilling(candidate[index])));
1788
1785
  // The chosen layout must be one the program actually builds.
1789
1786
  return (fillings.find((candidate, at) => minted[at] === true && differs(candidate) && foldable(candidate) && fillings.every((other) => absorbs(candidate, other))) ?? null);
1790
1787
  })();
@@ -6,6 +6,7 @@ import { classFamilyOverridesOf } from '../../projection/dispatch.js';
6
6
  export { classFamilyOverridesOf };
7
7
  import { classMemberOf, classStaticMemberOf, type ClassMemberSite } from '../../projection/fields.js';
8
8
  export { classMemberOf, classStaticMemberOf, type ClassMemberSite };
9
+ import type { ConstantLiteral } from '../../semantics/model/operands.js';
9
10
  import { type RecordField, type Representation } from '../../representation/model.js';
10
11
  import { type ConversionSite } from './emit-narrowing.js';
11
12
  /**
@@ -167,6 +168,45 @@ export declare const lazyArrowFieldGetPlanOf: (ctx: Pick<EmitContext, "classes"
167
168
  * the snapshot in hand.
168
169
  */
169
170
  export declare const lazyCalleeReadsOf: (ctx: EmitContext, body: IrBody) => ReadonlySet<IrValueId>;
171
+ /**
172
+ * A class field initializer that is one constant and nothing else.
173
+ *
174
+ * Carried as the constant's own `text`/`literal` rather than as a decided
175
+ * boolean because the question "is this redundant" cannot be answered here:
176
+ * it depends on the PHYSICAL storage the field was given, which
177
+ * `cppFieldInitializerStatements` learns from its `storedFieldOf` and this
178
+ * census never sees. This says what the initializer is; that site says what
179
+ * the storage is; the two together decide.
180
+ */
181
+ export interface ConstantFieldInitializer {
182
+ readonly text: string;
183
+ readonly literal: ConstantLiteral;
184
+ }
185
+ /** Publishes one compile's constant-field-initializer census. Called once, before any body renders -- mirrors `publishLazyArrowFieldPlans`. */
186
+ export declare const publishConstantFieldInitializers: (classes: ReadonlyMap<DeclarationId, ClassLayout>, constants: ReadonlyMap<DeclarationId, ReadonlyMap<string, ConstantFieldInitializer>>) => void;
187
+ /** The single constant one class field's initializer evaluates to, or `null` when it is anything else (including in a compile that published no census). */
188
+ export declare const constantFieldInitializerOf: (classes: ReadonlyMap<DeclarationId, ClassLayout>, declaration: DeclarationId, key: string) => ConstantFieldInitializer | null;
189
+ /**
190
+ * Every class field whose initializer is a single constant.
191
+ *
192
+ * The shape admitted is the narrowest one that proves "evaluating this thunk
193
+ * does nothing but produce this value": one block, one operation in it, that
194
+ * operation a `constant`, and the block returning that constant's own result.
195
+ * A thunk with two operations may have computed something; one that returns a
196
+ * different value than it built is not what it looks like. Neither gets in.
197
+ *
198
+ * A REACTIVE field is refused. Its storage is `Cell<T>`, not `T`
199
+ * (`records.ts`'s `fieldStorageType`), so the assignment runs the cell's own
200
+ * `operator=` and what a value-initialized cell holds is the cell's business,
201
+ * not this function's. The refusal reads the plugin's own per-class statement
202
+ * (`ReactiveCellPlan.fields`) rather than `cell`: gea names a cell spelling in
203
+ * every compile, reactive or not, so testing `cell !== null` would switch the
204
+ * census off for every program. It also cannot wait for the settled
205
+ * `celledFields`, which `cppRecordDeclarations` produces only after this
206
+ * census has to be published -- but that set is derived from these same
207
+ * `fields`, so refusing all of them refuses a superset.
208
+ */
209
+ export declare const censusConstantFieldInitializers: (bodies: readonly IrBody[], classes: ReadonlyMap<DeclarationId, ClassLayout>, reactiveFields: ReadonlyMap<DeclarationId, ReadonlySet<string>>) => ReadonlyMap<DeclarationId, ReadonlyMap<string, ConstantFieldInitializer>>;
170
210
  /**
171
211
  * Which class fields qualify for lazy materialization, computed once for the
172
212
  * whole translation unit.
@@ -62,6 +62,31 @@ classDeclaration, fields, receiver, storedFieldOf) => {
62
62
  return `field "${field.key}" initializer has no complete physical storage contract`;
63
63
  }
64
64
  const stored = storage.value;
65
+ // A field whose initializer writes the value its storage ALREADY holds.
66
+ //
67
+ // `makeRef<T>()` value-initializes -- these structs have no user-provided
68
+ // constructor, so `T()` zero-initializes and then default-constructs every
69
+ // member: a `std::string` field is `""` and a `bool` field is `false`
70
+ // before any statement here runs. `statusMessage = ''` then calls the
71
+ // field's initializer thunk and assigns its result, and for a
72
+ // `std::string` that assignment is an out-of-line libstdc++ `_M_replace`
73
+ // -- per field, per construction. `node:http`'s `ServerResponse` alone
74
+ // declares five, and it is built once per request.
75
+ //
76
+ // Only the store is dropped, and only where dropping it is invisible:
77
+ // the initializer must BE one constant (`censusConstantFieldInitializers`
78
+ // proved the thunk is a single `constant` and a `return` of it, so it has
79
+ // no side effect to lose), its representation must already be the
80
+ // storage's (no conversion to skip), the field must be required (an
81
+ // optional's presence bit is `false` by declaration and would still need
82
+ // setting), and the constant must be the carrier's own value-initialized
83
+ // value.
84
+ const constant = constantFieldInitializerOf(site.classes, classDeclaration, field.key);
85
+ if (constant !== null &&
86
+ storage.required &&
87
+ representationKey(field.representation) === representationKey(stored) &&
88
+ storesTheValueInitializedDefault(stored, constant))
89
+ continue;
65
90
  const markPresent = !storage.required ? ` ${receiver}->${cppRecordFieldPresenceName(field.key)} = true;` : '';
66
91
  // A widening may inspect a tagged union's live arm and therefore name its
67
92
  // input more than once. A field initializer runs exactly once, so bind its
@@ -236,6 +261,93 @@ export const lazyCalleeReadsOf = (ctx, body) => {
236
261
  }
237
262
  return result;
238
263
  };
264
+ const constantFieldInitializerSidecar = new WeakMap();
265
+ /** Publishes one compile's constant-field-initializer census. Called once, before any body renders -- mirrors `publishLazyArrowFieldPlans`. */
266
+ export const publishConstantFieldInitializers = (classes, constants) => {
267
+ constantFieldInitializerSidecar.set(classes, constants);
268
+ };
269
+ /** The single constant one class field's initializer evaluates to, or `null` when it is anything else (including in a compile that published no census). */
270
+ export const constantFieldInitializerOf = (classes, declaration, key) => constantFieldInitializerSidecar.get(classes)?.get(declaration)?.get(key) ?? null;
271
+ /**
272
+ * Whether `storage` value-initializes to exactly what `constant` spells.
273
+ *
274
+ * Deliberately only the two carriers whose C++ value-initialized state is a
275
+ * single known value: `std::string()` is empty and a scalar is zero. Anything
276
+ * else -- an optional, a union, a `Ref`, a record -- either has a presence or
277
+ * tag byte the caller must still write or a default this cannot state, and
278
+ * gets no answer rather than a guessed one.
279
+ *
280
+ * `-0` is refused although `Number('-0') === 0`: a value-initialized `double`
281
+ * is `+0.0`, and the two differ under `Object.is` and `1 / x`. Any negative
282
+ * spelling that reaches here is one of that family, so the sign test is the
283
+ * whole check.
284
+ */
285
+ const storesTheValueInitializedDefault = (storage, constant) => {
286
+ if (storage.kind === 'string')
287
+ return constant.literal === 'string' && constant.text === '';
288
+ if (storage.kind !== 'scalar')
289
+ return false;
290
+ if (storage.domain === 'boolean')
291
+ return constant.literal === 'boolean' && constant.text === 'false';
292
+ if (storage.domain === 'bigint')
293
+ return false;
294
+ return constant.literal === 'number' && !constant.text.startsWith('-') && Number(constant.text) === 0;
295
+ };
296
+ /**
297
+ * Every class field whose initializer is a single constant.
298
+ *
299
+ * The shape admitted is the narrowest one that proves "evaluating this thunk
300
+ * does nothing but produce this value": one block, one operation in it, that
301
+ * operation a `constant`, and the block returning that constant's own result.
302
+ * A thunk with two operations may have computed something; one that returns a
303
+ * different value than it built is not what it looks like. Neither gets in.
304
+ *
305
+ * A REACTIVE field is refused. Its storage is `Cell<T>`, not `T`
306
+ * (`records.ts`'s `fieldStorageType`), so the assignment runs the cell's own
307
+ * `operator=` and what a value-initialized cell holds is the cell's business,
308
+ * not this function's. The refusal reads the plugin's own per-class statement
309
+ * (`ReactiveCellPlan.fields`) rather than `cell`: gea names a cell spelling in
310
+ * every compile, reactive or not, so testing `cell !== null` would switch the
311
+ * census off for every program. It also cannot wait for the settled
312
+ * `celledFields`, which `cppRecordDeclarations` produces only after this
313
+ * census has to be published -- but that set is derived from these same
314
+ * `fields`, so refusing all of them refuses a superset.
315
+ */
316
+ export const censusConstantFieldInitializers = (bodies, classes, reactiveFields) => {
317
+ const result = new Map();
318
+ const bodyBySourceOwner = new Map();
319
+ for (const body of bodies)
320
+ bodyBySourceOwner.set(body.sourceOwner, body);
321
+ for (const [declaration, layout] of classes) {
322
+ for (const field of layout.fields) {
323
+ if (field.initializer === null)
324
+ continue;
325
+ if (reactiveFields.get(declaration)?.has(field.key))
326
+ continue;
327
+ const thunkBody = bodyBySourceOwner.get(field.initializer);
328
+ if (!thunkBody || thunkBody.blocks.size !== 1)
329
+ continue;
330
+ const block = [...thunkBody.blocks.values()][0];
331
+ if (!block || block.operations.length !== 1)
332
+ continue;
333
+ const only = block.operations[0];
334
+ if (!only || only.kind !== 'constant')
335
+ continue;
336
+ const terminator = block.terminator;
337
+ if (terminator.kind !== 'return' || terminator.value === null)
338
+ continue;
339
+ if (terminator.value.value !== only.result.id)
340
+ continue;
341
+ let byKey = result.get(declaration);
342
+ if (!byKey) {
343
+ byKey = new Map();
344
+ result.set(declaration, byKey);
345
+ }
346
+ byKey.set(field.key, { text: only.text, literal: only.literal });
347
+ }
348
+ }
349
+ return result;
350
+ };
239
351
  /**
240
352
  * Which class fields qualify for lazy materialization, computed once for the
241
353
  * whole translation unit.
@@ -3,7 +3,7 @@ import { abiKey, representationKey } from '../../../representation/model.js';
3
3
  import { abiOfCallee } from '../../../projection/callee.js';
4
4
  import { runtimeClassLayoutsOf } from '../../../projection/classes.js';
5
5
  import { classMethodOverrideOf, classPrototypeMethodMutableOf } from '../../../projection/fields.js';
6
- import { bindingReference, captureFieldText, cppEnvironmentStructName, cppThunkName, createCppEmitBlockedError, operandText } from '../emit-context.js';
6
+ import { bindingReference, captureFieldText, cppEnvironmentStructName, cppThunkEntryText, createCppEmitBlockedError, operandText } from '../emit-context.js';
7
7
  import { classFamilyOverridesOf, classMemberOf, dispatchesStatically, reachableClassMethodsOf, classStaticFieldStorageOf, classStaticMemberOf } from '../class-layout.js';
8
8
  import { cppVirtualMemberName, virtualDispatchKey } from '../virtual-methods.js';
9
9
  import { cppAbiParameterType, cppBodyName, cppCallableDeclarationTagName, cppClassName, cppConstructName, cppRecordFieldName, cppRecordFieldPresenceName, cppResultTypeOf, cppStringLiteral, cppTypeOf, cppUndefinedIn } from '../types.js';
@@ -160,7 +160,7 @@ valueRepresentation = boundMethodValueRepresentation(operation), receiverText =
160
160
  const owner = [...ctx.classes.values()].find((layout) => layout.methods.some((entry) => entry.callable === method.callable && entry.key === key));
161
161
  if (owner === undefined)
162
162
  throw createCppEmitBlockedError('call-abi:class-method-owner', `method "${key}" has no declaring prototype`);
163
- const payload = `${cppTypeOf(bodyRepresentation)}{&${cppThunkName(method.callable)}, ${environment}}`;
163
+ const payload = `${cppTypeOf(bodyRepresentation)}{${cppThunkEntryText(ctx, method.callable)}, ${environment}}`;
164
164
  const override = receiverRepresentation.kind === 'class-ref' && !dispatchesStatically(ctx, operation.receiver)
165
165
  ? classMethodOverrideOf(ctx.classes, receiverRepresentation.declaration, key)
166
166
  : null;
@@ -813,7 +813,7 @@ receiverText) => {
813
813
  throw createCppEmitBlockedError('property-access:class-prototype:method-abi', 'prototype method has no native convention');
814
814
  const representation = { kind: 'function-value-dispatch', abi };
815
815
  const environment = methodEnvironmentText(ctx, method.callable, `prototype method ${method.key}`);
816
- const payload = `${cppTypeOf(representation)}{&${cppThunkName(method.callable)}, ${environment}}`;
816
+ const payload = `${cppTypeOf(representation)}{${cppThunkEntryText(ctx, method.callable)}, ${environment}}`;
817
817
  return {
818
818
  representation,
819
819
  text: `gea::nativeClassMethodValue<${cppClassName(owner.declaration)}, &${cppCallableDeclarationTagName(method.callable)}>(${state}, ${payload})`
@@ -869,7 +869,7 @@ receiverText) => {
869
869
  // `gea::Value{&thunk, env}` for a computed read, which is not a boxed
870
870
  // callable at all.
871
871
  const methodAbi = ctx.abiOfCallable(site.method.callable);
872
- const object = `{&${cppThunkName(site.method.callable)}, ${methodEnvironmentText(ctx, site.method.callable, `a "get" of static "${key}"`)}}`;
872
+ const object = `{${cppThunkEntryText(ctx, site.method.callable)}, ${methodEnvironmentText(ctx, site.method.callable, `a "get" of static "${key}"`)}}`;
873
873
  if (methodAbi === null)
874
874
  return `${cppTypeOf(result)}${object}`;
875
875
  const methodRepresentation = { kind: 'function-value-dispatch', abi: methodAbi };
@@ -11,7 +11,7 @@ import { cppTypeOf } from './types.js';
11
11
  import { nativeSumWidenable } from '../../conversion/native-sum.js';
12
12
  import { nativeSelectionRecipeOf, nativeTotalSelectionRecipeOf } from '../../conversion/native-selection.js';
13
13
  import { nativeClassReferenceIdentityOf } from '../../conversion/native-class-reference.js';
14
- import { ownedRecordMaterializationPlan, recordViewUsesOnlyDirectFields } from '../../conversion/record-view.js';
14
+ import { isRecordViewTarget, ownedRecordMaterializationPlan, recordViewDispatchesArms, recordViewUsesOnlyDirectFields } from '../../conversion/record-view.js';
15
15
  import { cppStringObjectNativeType } from './regexp-types.js';
16
16
  import { viewPlanFor } from './emit-record-view.js';
17
17
  /** Called only after the native converting-constructor predicate has selected this pair. */
@@ -446,6 +446,37 @@ const dynamicCallablePair = (target) => {
446
446
  materializer: { id: 'gea::detail::DynamicCarrier::in', domain, allocates: true }
447
447
  };
448
448
  };
449
+ /**
450
+ * A shared class handle among a sum's arms, at a record-shaped target it
451
+ * reaches by neither the chain nor the structural view.
452
+ *
453
+ * Nothing in the program proves that arm dead: an interface-typed slot holds
454
+ * whatever object the program put there (`semantics/interface-implementors.ts`),
455
+ * and a class this cannot view as the record is still a value the slot can be
456
+ * handed -- skytail's `const ctx: AudioContextLike | null = Ctor ? new Ctor()
457
+ * : createNativeAudioContext()`, whose `NativeAudioContext` carries class-typed
458
+ * fields and a promise the view cannot rebuild. Selecting the record arm
459
+ * there read the class instance's bytes as the record and crashed at launch,
460
+ * certified. A record arm beside the exact one is the opposite case: a record
461
+ * the view cannot rebuild as the target is not assignable to it either, so the
462
+ * checker's own narrowing is what put the pair here, and the selection stands.
463
+ *
464
+ * Asked by every table that could answer the pair -- `narrowing` and
465
+ * `staticRecipe` -- so the census refuses it outright rather than one table
466
+ * declining and the next selecting.
467
+ */
468
+ const classArmWithoutHome = (layouts, source, target) => {
469
+ const union = source.kind === 'optional' ? source.payload : source;
470
+ const selected = target.kind === 'optional' ? target.payload : target;
471
+ if (union.kind !== 'tagged-union' || !isRecordViewTarget(selected))
472
+ return false;
473
+ const key = representationKey(selected);
474
+ return union.arms.some(({ value }) => value.kind === 'class-ref' &&
475
+ value.ownership === 'shared-refcount' &&
476
+ representationKey(value) !== key &&
477
+ conversionRecipeOf(value, selected)?.renders !== true &&
478
+ viewPlanFor(layouts, value, selected) === null);
479
+ };
449
480
  export const createCppConversionRegistry = (layouts = defaultRecordLayoutPolicy) => {
450
481
  // A sum narrowing's contract is composed from the contracts this same
451
482
  // registry states for its leaf pairs (`native-narrowing-transport.ts`), so
@@ -791,6 +822,8 @@ const cppConversionTables = (layouts, nativeNarrowing, nativeWrappedPayload) =>
791
822
  // can see, and whose lifetime the program never stated.
792
823
  if ('ownership' in target && target.ownership === 'borrowed')
793
824
  return null;
825
+ if (classArmWithoutHome(layouts, source, target))
826
+ return null;
794
827
  const setPair = genericFunctionSetPair(source, target);
795
828
  if (setPair)
796
829
  return setPair;
@@ -949,6 +982,18 @@ const cppConversionTables = (layouts, nativeNarrowing, nativeWrappedPayload) =>
949
982
  const selected = source.kind === 'optional' && target.kind === 'optional' ? target.payload : target;
950
983
  if (union.kind !== 'tagged-union')
951
984
  return null;
985
+ // A record-shaped target some OTHER arm reaches only through the
986
+ // structural view is not a selection: that arm is live at a store, and a
987
+ // selection would read it as the exact arm. The view's `dispatch` plan
988
+ // (`conversion/record-view.ts`) is the conversion, installed by
989
+ // `staticRecipe` below once this table declines -- the same plan
990
+ // `emit-narrowing.ts`'s `narrowedLoadText` defers to, asked here so the
991
+ // admission and the render agree.
992
+ if (isRecordViewTarget(selected)) {
993
+ const view = viewPlanFor(layouts, source, target);
994
+ if (view !== null && recordViewDispatchesArms(view))
995
+ return null;
996
+ }
952
997
  const targetKey = representationKey(selected);
953
998
  // A SUB-union target: `selected` is still a tagged union, just missing one
954
999
  // or more of `union`'s arms -- `h instanceof Headers`'s `else` branch
@@ -1972,6 +2017,22 @@ const cppConversionTables = (layouts, nativeNarrowing, nativeWrappedPayload) =>
1972
2017
  staticRecipe: (source, target) => {
1973
2018
  if (!isSpellable(source) || !isSpellable(target))
1974
2019
  return null;
2020
+ if (classArmWithoutHome(layouts, source, target))
2021
+ return null;
2022
+ // A sum some arm of which reaches the record-shaped target only through
2023
+ // the structural view is that view's `dispatch` plan, asked BEFORE the
2024
+ // chain: the chain answers the pair too, by selecting the exact arm, and
2025
+ // that selection is the narrowing this table's `narrowing` entry has
2026
+ // already declined for the pair (`recordViewDispatchesArms`). The
2027
+ // printer renders in the same order (`emit-narrowing.ts`'s `recipeText`).
2028
+ const dispatching = viewPlanFor(layouts, source, target);
2029
+ if (dispatching !== null && recordViewDispatchesArms(dispatching))
2030
+ return {
2031
+ id: 'view:structural-record',
2032
+ domain: 'static:structural-record-view',
2033
+ allocates: true,
2034
+ ...(recordViewUsesOnlyDirectFields(dispatching) ? { nativeFieldProtocol: 'unused' } : {})
2035
+ };
1975
2036
  const recipe = conversionRecipeOf(source, target);
1976
2037
  if (recipe !== null && recipe.renders) {
1977
2038
  // `unreachable-value` spells either `unreachableValue<T>()` (a throw that
@@ -2022,6 +2083,7 @@ const allocatingRecipes = new Set([
2022
2083
  'record-recast',
2023
2084
  'optional-record-recast',
2024
2085
  'record-to-dictionary',
2086
+ 'dictionary-to-record',
2025
2087
  'record-to-array',
2026
2088
  'promise-payload'
2027
2089
  ]);