@geastack/compiler 1.0.16 → 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 (47) 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/lower-operands.d.ts +10 -1
  7. package/dist/ir/lower-operands.js +58 -2
  8. package/dist/ir/lower.js +12 -3
  9. package/dist/ir/reflection-demand.js +5 -0
  10. package/dist/plugins/apple/host.d.ts +2 -0
  11. package/dist/plugins/apple/host.js +13 -6
  12. package/dist/plugins/gea/host.d.ts +2 -0
  13. package/dist/plugins/gea/host.js +11 -4
  14. package/dist/plugins/host-package.d.ts +37 -0
  15. package/dist/plugins/host-package.js +28 -0
  16. package/dist/plugins/load.js +15 -0
  17. package/dist/plugins/webgl/host.d.ts +9 -3
  18. package/dist/plugins/webgl/host.js +15 -6
  19. package/dist/plugins/webgl/plugin.js +4 -1
  20. package/dist/representation/embedded-carriers.d.ts +22 -0
  21. package/dist/representation/embedded-carriers.js +105 -0
  22. package/dist/semantics/model/operands.d.ts +17 -0
  23. package/dist/semantics/model/operations.d.ts +28 -1
  24. package/dist/semantics/normalize/producers/allocations.js +2 -0
  25. package/dist/semantics/normalize/producers/bindings.js +5 -1
  26. package/dist/semantics/normalize/producers/boundary.d.ts +1 -0
  27. package/dist/semantics/normalize/producers/boundary.js +6 -2
  28. package/dist/semantics/normalize/producers/class-lifecycle.js +3 -0
  29. package/dist/semantics/normalize/producers/control.js +2 -2
  30. package/dist/semantics/normalize/producers/erasure.d.ts +7 -0
  31. package/dist/semantics/normalize/producers/erasure.js +19 -0
  32. package/dist/semantics/normalize/producers/exact-arms.d.ts +12 -0
  33. package/dist/semantics/normalize/producers/exact-arms.js +12 -0
  34. package/dist/semantics/normalize/producers/invocations.js +33 -1
  35. package/dist/targets/cpp/class-properties/emit-class-properties.js +4 -4
  36. package/dist/targets/cpp/conversions.js +63 -1
  37. package/dist/targets/cpp/emit-callable.js +5 -5
  38. package/dist/targets/cpp/emit-context.d.ts +38 -1
  39. package/dist/targets/cpp/emit-context.js +28 -2
  40. package/dist/targets/cpp/emit-narrowing.d.ts +1 -1
  41. package/dist/targets/cpp/emit-narrowing.js +47 -1
  42. package/dist/targets/cpp/emit-record-view.js +31 -0
  43. package/dist/targets/cpp/emit.d.ts +3 -3
  44. package/dist/targets/cpp/emit.js +11 -5
  45. package/dist/targets/cpp/translation-unit.js +24 -15
  46. package/package.json +1 -1
  47. package/src/targets/cpp/runtime/gea_runtime.h +123 -44
@@ -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
  ]);
@@ -10,7 +10,7 @@ import { alignedValueText, bindsReceiver, callableObjectAbi, movedValueText, unb
10
10
  import { memberAccessOperator, reactiveRevisionText } from './emit-carrier-members.js';
11
11
  import { structuralRecordViewText } from './emit-record-view.js';
12
12
  import { emitDateConstruct, isDateCarrier } from './prototype/emit-prototype-date.js';
13
- import { unwrapPresentValue, bindingReference, cppConstructedThunkName, cppConstructThunkName, cppEnvironmentStructName, cppReceiverName, cppThunkName, createCppEmitBlockedError, declareCell, defineValue, defineValueAlias, isCppEmitBlockedError, isIntegerStorageValue, operandText, paddedArguments, captureFieldText } from './emit-context.js';
13
+ import { unwrapPresentValue, bindingReference, cppConstructedThunkName, cppConstructThunkName, cppEnvironmentStructName, cppReceiverName, cppThunkEntryText, cppThunkName, createCppEmitBlockedError, declareCell, defineValue, defineValueAlias, isCppEmitBlockedError, isIntegerStorageValue, operandText, paddedArguments, captureFieldText } from './emit-context.js';
14
14
  import { classMemberOf, classStaticMemberOf, cppFieldInitializerStatements, lazyArrowFieldPlanOf } from './class-layout.js';
15
15
  import { declaredRecordFieldOf } from './records.js';
16
16
  import { hostArityCallLines, hostResultText } from './host/emit-host-arity.js';
@@ -1501,8 +1501,8 @@ export const emitAllocateCallable = (ctx, lines, operation) => {
1501
1501
  // Brace-initializing this carrier with two values is what used to leave every
1502
1502
  // pre-`class` constructor function in three.js's renderer unclaimed.
1503
1503
  const pointers = payloadCarrier.kind === 'function-and-constructor'
1504
- ? `&${cppThunkName(operation.functionId)}, &${cppConstructedThunkName(operation.functionId)}`
1505
- : `&${cppThunkName(operation.functionId)}`;
1504
+ ? `${cppThunkEntryText(ctx, operation.functionId)}, &${cppConstructedThunkName(operation.functionId)}`
1505
+ : cppThunkEntryText(ctx, operation.functionId);
1506
1506
  // Tagged by the SOURCE DECLARATION, not by this copy's thunk. Two
1507
1507
  // instantiations of one generic function are two thunks and one JavaScript
1508
1508
  // function object; the runtime's own `CallableDeclarationTag` comment says
@@ -1551,11 +1551,11 @@ export const emitAllocateCallable = (ctx, lines, operation) => {
1551
1551
  return `gea::Value::boxCallable<${abi.restFrom}>(${callable})`;
1552
1552
  };
1553
1553
  if (admission.kind === 'none') {
1554
- lines.push(`${name} = gea::host::installOrdinaryConstructorPrototype(${boxText(`${callableType}{&${cppThunkName(operation.functionId)}, nullptr}`)});`);
1554
+ lines.push(`${name} = gea::host::installOrdinaryConstructorPrototype(${boxText(`${callableType}{${cppThunkEntryText(ctx, operation.functionId)}, nullptr}`)});`);
1555
1555
  return;
1556
1556
  }
1557
1557
  const dynamicEnvironment = packedEnvironmentText(ctx, lines, operation.functionId, admission);
1558
- lines.push(`${name} = gea::host::installOrdinaryConstructorPrototype(${boxText(`${callableType}{&${cppThunkName(operation.functionId)}, ${dynamicEnvironment}}`)});`);
1558
+ lines.push(`${name} = gea::host::installOrdinaryConstructorPrototype(${boxText(`${callableType}{${cppThunkEntryText(ctx, operation.functionId)}, ${dynamicEnvironment}}`)});`);
1559
1559
  return;
1560
1560
  }
1561
1561
  if (admission.kind === 'none') {
@@ -678,6 +678,13 @@ export interface EmitContext {
678
678
  readonly generatorBody: boolean;
679
679
  /** The conventions of emitted bodies, shared with their signature renderer. */
680
680
  readonly abiOfCallable: (callable: FunctionId) => CallableAbi | null;
681
+ /**
682
+ * The `name`/`length`/source text each function object must be able to
683
+ * answer, keyed by the body -- empty when the program never reads any of
684
+ * them (`translation-unit.ts`'s `preserveFunctionFacts` census). Read by
685
+ * `cppThunkEntryText`, at the sites that mint a function object.
686
+ */
687
+ readonly functionFacts: ReadonlyMap<FunctionId, CallableFactsSpelling>;
681
688
  /** The execution context whose frame this body is, so a cell owned elsewhere is recognisable as one. */
682
689
  readonly owner: FunctionId | RegionId;
683
690
  /** See `emit-narrowing.ts`'s `PrinterDrift`; one program-wide list, shared by every body's context. */
@@ -1622,7 +1629,7 @@ export declare const directCalleesOf: (body: IrBody, abiOfCallable: (callable: F
1622
1629
  readonly directCallees: ReadonlyMap<IrValueId, string>;
1623
1630
  readonly directCalleeAbis: ReadonlyMap<IrValueId, CallableAbi>;
1624
1631
  };
1625
- export declare const createEmitContext: (abi: CallableAbi | null, owner: FunctionId | RegionId, placements: ReadonlyMap<DeclarationId, BindingPlacement>, classes: ReadonlyMap<DeclarationId, ClassLayout>, hosts: HostSpellings, deriver: RepresentationDeriver, bodyFacts: EmitBodyFacts, wellKnownSymbols?: ReadonlyMap<DeclarationId, string>, captures?: CaptureIndex, symbolKeys?: Map<string, string>, templateObjects?: Map<string, TemplateObjectDefinition>, directCallableBindings?: ReadonlyMap<DeclarationId, FunctionId>, virtualDispatch?: ReadonlyMap<string, CallableAbi>, narrowedFormals?: ReadonlySet<number>, repeatedConstructors?: ReadonlyMap<DeclarationId, DeclarationId>, dyingArguments?: ReadonlySet<IrValueId>, instantiation?: InstantiationFacts, abiOfCallable?: (callable: FunctionId) => CallableAbi | null, hostMethodAliases?: ReadonlyMap<DeclarationId, HostMethodAlias>, callableMemberCandidates?: ReadonlyMap<string, FunctionId>, borrowableMemberBodies?: ReadonlySet<FunctionId>, stableBorrowEntries?: ReadonlyMap<string, StableBorrowEntry>, printerDrift?: PrinterDrift[], conversions?: ConversionCensus | null, nativeSelections?: ReadonlyMap<string, NativeSelectionHelper> | undefined, callableIdentityDemand?: CallableIdentityDemand, nativeIntegrityRestricted?: boolean, fixedFieldStateConstant?: boolean) => {
1632
+ export declare const createEmitContext: (abi: CallableAbi | null, owner: FunctionId | RegionId, placements: ReadonlyMap<DeclarationId, BindingPlacement>, classes: ReadonlyMap<DeclarationId, ClassLayout>, hosts: HostSpellings, deriver: RepresentationDeriver, bodyFacts: EmitBodyFacts, wellKnownSymbols?: ReadonlyMap<DeclarationId, string>, captures?: CaptureIndex, symbolKeys?: Map<string, string>, templateObjects?: Map<string, TemplateObjectDefinition>, directCallableBindings?: ReadonlyMap<DeclarationId, FunctionId>, virtualDispatch?: ReadonlyMap<string, CallableAbi>, narrowedFormals?: ReadonlySet<number>, repeatedConstructors?: ReadonlyMap<DeclarationId, DeclarationId>, dyingArguments?: ReadonlySet<IrValueId>, instantiation?: InstantiationFacts, abiOfCallable?: (callable: FunctionId) => CallableAbi | null, functionFacts?: ReadonlyMap<FunctionId, CallableFactsSpelling>, hostMethodAliases?: ReadonlyMap<DeclarationId, HostMethodAlias>, callableMemberCandidates?: ReadonlyMap<string, FunctionId>, borrowableMemberBodies?: ReadonlySet<FunctionId>, stableBorrowEntries?: ReadonlyMap<string, StableBorrowEntry>, printerDrift?: PrinterDrift[], conversions?: ConversionCensus | null, nativeSelections?: ReadonlyMap<string, NativeSelectionHelper> | undefined, callableIdentityDemand?: CallableIdentityDemand, nativeIntegrityRestricted?: boolean, fixedFieldStateConstant?: boolean) => {
1626
1633
  readonly ctx: EmitContext;
1627
1634
  readonly prepass: EmitBodyPrepassFacts;
1628
1635
  };
@@ -1739,6 +1746,36 @@ export declare const cppFormalName: (ordinal: number) => string;
1739
1746
  export declare const cppReceiverName = "gea_this";
1740
1747
  /** The thunk that adapts one function body to the environment-passing invoke pointer a callable carrier holds. */
1741
1748
  export declare const cppThunkName: (functionId: string) => string;
1749
+ /**
1750
+ * What one function object answers for `name`, `length` and
1751
+ * `Function.prototype.toString`, already spelled for the registration call:
1752
+ * `abiType` is `cppAbiType` of the thunk's own convention, which the
1753
+ * registry's `Invoke` template parameter must match exactly.
1754
+ */
1755
+ export interface CallableFactsSpelling {
1756
+ readonly abiType: string;
1757
+ readonly name: string;
1758
+ readonly length: number;
1759
+ readonly source: string;
1760
+ }
1761
+ /**
1762
+ * The invoke pointer a minting site stores in a callable carrier: `&thunk`,
1763
+ * or -- when the program reads function facts -- `&thunk` handed back by
1764
+ * `gea::CallableObject<Abi>::entryWithFacts<&thunk>(name, length, text)`,
1765
+ * which registers the facts the first time any site mints this function.
1766
+ *
1767
+ * Registration lives HERE, at the mint, and not beside the thunk, because a
1768
+ * namespace-scope `static const bool ... = registerSource<&thunk>(...)` is a
1769
+ * static initializer that takes the thunk's address, and a static initializer
1770
+ * with a side effect is a root the linker may not drop. Every emitted
1771
+ * function, and its source text, then survived `--gc-sections` and LTO
1772
+ * whether or not anything reached it: a raw HTTP server whose `EventEmitter`
1773
+ * boxed one listener kept all 384 of its functions. A mint site is reachable
1774
+ * exactly when a function object can exist, and only an existing function
1775
+ * object can be asked for its facts, so registering there preserves every
1776
+ * observable answer and pins nothing else.
1777
+ */
1778
+ export declare const cppThunkEntryText: (ctx: EmitContext, functionId: FunctionId) => string;
1742
1779
  /** The thunk that adapts one class's construct function to the environment-passing construct pointer a constructor carrier holds. */
1743
1780
  export declare const cppConstructThunkName: (declaration: DeclarationId) => string;
1744
1781
  /**
@@ -4,7 +4,7 @@ import { noInstantiationFacts } from '../../ir/instantiation.js';
4
4
  import { observesEveryCallableIdentity } from '../../ir/callable-identity-demand.js';
5
5
  import { hostCallName, hostMemberOf, statedHostIntrinsicLength } from './host/host-members.js';
6
6
  import { representationKey } from '../../representation/model.js';
7
- import { cppBodyName, cppConstructName, cppNarrowedIntegerType, cppStringLiteral, cppTypeOf, cppUndefinedIn } from './types.js';
7
+ import { cppBodyName, cppConstructName, cppNarrowedIntegerType, cppStringLiteral, cppStringViewLiteral, cppTypeOf, cppUndefinedIn } from './types.js';
8
8
  import { hostBuiltinFunctionIdentityText, hostClassValueText, hostFunctionValueText, hostJsonMemberValueText, hostMemberValueText } from './host/emit-host-value.js';
9
9
  import { createConversionNodes } from '../../conversion/nodes.js';
10
10
  import { createCppConversionRegistry } from './conversions.js';
@@ -75,7 +75,7 @@ export const directCalleesOf = (body, abiOfCallable) => {
75
75
  }
76
76
  return { directCallees, directCalleeAbis };
77
77
  };
78
- export const createEmitContext = (abi, owner, placements, classes, hosts, deriver, bodyFacts, wellKnownSymbols = new Map(), captures = emptyCaptureIndex, symbolKeys = new Map(), templateObjects = new Map(), directCallableBindings = new Map(), virtualDispatch = new Map(), narrowedFormals = new Set(), repeatedConstructors = new Map(), dyingArguments = new Set(), instantiation = noInstantiationFacts, abiOfCallable = () => null, hostMethodAliases = new Map(), callableMemberCandidates = new Map(), borrowableMemberBodies = new Set(), stableBorrowEntries = new Map(), printerDrift = [],
78
+ export const createEmitContext = (abi, owner, placements, classes, hosts, deriver, bodyFacts, wellKnownSymbols = new Map(), captures = emptyCaptureIndex, symbolKeys = new Map(), templateObjects = new Map(), directCallableBindings = new Map(), virtualDispatch = new Map(), narrowedFormals = new Set(), repeatedConstructors = new Map(), dyingArguments = new Set(), instantiation = noInstantiationFacts, abiOfCallable = () => null, functionFacts = new Map(), hostMethodAliases = new Map(), callableMemberCandidates = new Map(), borrowableMemberBodies = new Set(), stableBorrowEntries = new Map(), printerDrift = [],
79
79
  // A context built without the program's census (the value-contract
80
80
  // checks) gets one over the same tables with nothing pre-minted: the
81
81
  // registry answers every pair the eager graph would have, one node later.
@@ -126,6 +126,7 @@ conversions = null, nativeSelections = undefined, callableIdentityDemand = obser
126
126
  const ctx = {
127
127
  abi: effectiveAbiOf(abi, admission),
128
128
  abiOfCallable,
129
+ functionFacts,
129
130
  owner,
130
131
  placements,
131
132
  classes,
@@ -537,6 +538,31 @@ export const cppFormalName = (ordinal) => `gea_arg_${ordinal}`;
537
538
  export const cppReceiverName = 'gea_this';
538
539
  /** The thunk that adapts one function body to the environment-passing invoke pointer a callable carrier holds. */
539
540
  export const cppThunkName = (functionId) => `${cppBodyName(functionId)}_thunk`;
541
+ /**
542
+ * The invoke pointer a minting site stores in a callable carrier: `&thunk`,
543
+ * or -- when the program reads function facts -- `&thunk` handed back by
544
+ * `gea::CallableObject<Abi>::entryWithFacts<&thunk>(name, length, text)`,
545
+ * which registers the facts the first time any site mints this function.
546
+ *
547
+ * Registration lives HERE, at the mint, and not beside the thunk, because a
548
+ * namespace-scope `static const bool ... = registerSource<&thunk>(...)` is a
549
+ * static initializer that takes the thunk's address, and a static initializer
550
+ * with a side effect is a root the linker may not drop. Every emitted
551
+ * function, and its source text, then survived `--gc-sections` and LTO
552
+ * whether or not anything reached it: a raw HTTP server whose `EventEmitter`
553
+ * boxed one listener kept all 384 of its functions. A mint site is reachable
554
+ * exactly when a function object can exist, and only an existing function
555
+ * object can be asked for its facts, so registering there preserves every
556
+ * observable answer and pins nothing else.
557
+ */
558
+ export const cppThunkEntryText = (ctx, functionId) => {
559
+ const thunk = `&${cppThunkName(functionId)}`;
560
+ const facts = ctx.functionFacts.get(functionId);
561
+ if (facts === undefined)
562
+ return thunk;
563
+ return (`gea::CallableObject<${facts.abiType}>::entryWithFacts<${thunk}>(` +
564
+ `${cppStringViewLiteral(facts.name)}, ${facts.length}, ${cppStringViewLiteral(facts.source)})`);
565
+ };
540
566
  /** The thunk that adapts one class's construct function to the environment-passing construct pointer a constructor carrier holds. */
541
567
  export const cppConstructThunkName = (declaration) => `${cppConstructName(declaration)}_thunk`;
542
568
  /**
@@ -3,7 +3,7 @@ import type { CallableAbi, Representation, TaggedUnionArm } from '../../represen
3
3
  import type { IrOperand, MergeLiveArmRebuildOperation } from '../../ir/model.js';
4
4
  import type { DeclarationId, FunctionId } from '../../identity/ids.js';
5
5
  import type { ConversionNode } from '../../conversion/algebra.js';
6
- import type { ConversionCensus } from '../../conversion/nodes.js';
6
+ import { type ConversionCensus } from '../../conversion/nodes.js';
7
7
  import type { RecordLayoutPolicy } from '../../representation/policies.js';
8
8
  import type { ClassLayout } from '../../projection/classes.js';
9
9
  import type { CaptureIndex } from './emit-context.js';
@@ -2,7 +2,9 @@ import { classRefTransportKind, constructorUpcastMember } from './class-ref-tran
2
2
  import { abiKey, arrayExtensionKey, containsUnresolved, isBooleanShapedMergeTarget, isCanonicalNumberPropertyKeyText, representationKey, walkRepresentation, carriesUndefined } from '../../representation/model.js';
3
3
  import { transferOf } from '../../ir/transfer.js';
4
4
  import { coercionText } from './emit-coercion.js';
5
- import { structuralRecordViewText } from './emit-record-view.js';
5
+ import { EXACT_ARM_MATERIALIZER, exactArmIndexOf } from '../../conversion/nodes.js';
6
+ import { structuralRecordViewText, viewPlanFor } from './emit-record-view.js';
7
+ import { recordViewDispatchesArms } from '../../conversion/record-view.js';
6
8
  import { createCppEmitBlockedError, cppConstructThunkName, cppThunkName, defineValue, isCppEmitBlockedError, isDeferredValue, operandText } from './emit-context.js';
7
9
  import { booleanTestText } from './emit-presence.js';
8
10
  import { recastedRecordToArrayText } from './emit-arrays.js';
@@ -476,6 +478,16 @@ export const narrowedLoadText = (held, read, text) => {
476
478
  }
477
479
  if (inner.kind !== 'tagged-union')
478
480
  return null;
481
+ // Every arm search below SELECTS: it loads the arm the read names and
482
+ // trusts the narrowing that licensed the load to have killed the rest. A
483
+ // store into a declared slot has no such licence -- `const ctx:
484
+ // AudioContextLike | null = Ctor ? new Ctor() : createNativeAudioContext()`
485
+ // converts a `record | class` into the interface's record, and the class
486
+ // arm is as live as the record's. This chain cannot tell the two apart (a
487
+ // class viewed as a record needs the program's layouts), so the census
488
+ // decides it: a pair some arm reaches only through the structural view is
489
+ // `conversion/record-view.ts`'s `dispatch` plan, installed and rendered
490
+ // ahead of this chain (`conversions.ts`'s `staticRecipe`, `recipeText`).
479
491
  // A join may preserve another join (or an optional) as one physical arm.
480
492
  // Search that nested carrier before trying to compare the outer arm list
481
493
  // with the read's list. Otherwise a read of the preserved inner union is
@@ -1907,6 +1919,14 @@ export const dictionaryCastableToDictionary = (source, target) => {
1907
1919
  throw error;
1908
1920
  }
1909
1921
  };
1922
+ const dictionaryToRecordText = (source, target, text) => {
1923
+ if (source.kind !== 'dictionary' || source.key !== 'string' || source.value.kind !== 'dynamic' || source.ownership !== 'shared-refcount')
1924
+ return null;
1925
+ if (target.kind !== 'record' || target.accessors.length !== 0 || target.ownership === 'borrowed')
1926
+ return null;
1927
+ const boxed = dynamicCarrierBoxText(source, text);
1928
+ return boxed === null ? null : unboxedLoadText(target, boxed);
1929
+ };
1910
1930
  const recastedDictionaryText = (source, target, text) => {
1911
1931
  if (!dictionaryCastableToDictionary(source, target))
1912
1932
  return null;
@@ -3435,6 +3455,18 @@ export const conversionChain = [
3435
3455
  id: 'dictionary-to-dictionary',
3436
3456
  apply: (source, target, text) => source.kind === 'dictionary' && target.kind === 'dictionary' ? claimed(recastedDictionaryText(source, target, text)) : undefined
3437
3457
  },
3458
+ // The mirror of `record-to-dictionary` for the one dictionary whose values
3459
+ // are boxed: `globalThis as unknown as { AudioContext?: Ctor }` reads named
3460
+ // fields out of the open string-keyed surface `references.ts` gives
3461
+ // `globalThis`. The closed field list comes from the TARGET, so this is not
3462
+ // the reconstruction `conversion/derive.ts` refuses (recovering a field list
3463
+ // from an open dictionary); it is the dynamic-object product read
3464
+ // (`unboxedLoadText`'s record branch) over that dictionary boxed, which
3465
+ // `gea::detail::dynamicRecordHasField` already reads as a document.
3466
+ {
3467
+ id: 'dictionary-to-record',
3468
+ apply: (source, target, text) => claimed(dictionaryToRecordText(source, target, text))
3469
+ },
3438
3470
  // An Array of one element carrier copied into an Array of another, each
3439
3471
  // element through its own conversion. A copy, so only for an array that is
3440
3472
  // not written through both names afterwards: a matcher table built once
@@ -3695,6 +3727,11 @@ export const recipeText = (ctx, node, text) => {
3695
3727
  // `conversions.ts`'s read of a structural value as a class nothing instantiates.
3696
3728
  if (node.capability.kind === 'atom' && node.capability.materializer.id === 'gea::host::unreachableValue')
3697
3729
  return `((void)(${text}), gea::host::unreachableValue<${cppTypeOf(node.target)}>())`;
3730
+ // The census's exact-arm projection (`nodes.ts`'s `exactArmFor`): the arm
3731
+ // index is a function of the pair, so it is re-derived here rather than
3732
+ // carried on the node.
3733
+ if (node.capability.kind === 'static' && node.capability.materializer.id === EXACT_ARM_MATERIALIZER)
3734
+ return `gea::host::exactArm<${exactArmIndexOf(node.source, node.target)}>(${text})`;
3698
3735
  // `conversions.ts`'s read of a class instance's structural view as the class: the boxed origin, narrowed.
3699
3736
  if (node.capability.kind === 'atom' && node.capability.materializer.id === 'gea::record::viewOrigin')
3700
3737
  return narrowedLoadText({ kind: 'dynamic', reason: 'declared-any-never-narrowed' }, node.target, node.source.kind === 'optional'
@@ -3706,6 +3743,15 @@ export const recipeText = (ctx, node, text) => {
3706
3743
  ? `${helper.name}(${text})`
3707
3744
  : nativeSelectionText(node.capability.materializer.nativeSelection, node.source, node.target, text);
3708
3745
  }
3746
+ // A view that DISPATCHES on a sum's live arm is rendered ahead of the
3747
+ // chain: the chain would select the exact arm and trust a narrowing that,
3748
+ // at a store, never happened (`narrowedLoadText`'s arm search says why).
3749
+ // `conversions.ts`'s `staticRecipe` installs the node in the same order.
3750
+ if (node.capability.kind === 'static' && node.capability.materializer.id === 'view:structural-record') {
3751
+ const view = viewPlanFor(ctx.layouts, node.source, node.target);
3752
+ if (view !== null && recordViewDispatchesArms(view))
3753
+ return structuralRecordViewText(ctx, node.source, node.target, text);
3754
+ }
3709
3755
  return convertedValueText(node.source, node.target, text) ?? structuralRecordViewText(ctx, node.source, node.target, text);
3710
3756
  };
3711
3757
  /**
@@ -266,6 +266,37 @@ const recordViewText = (ctx, plan, text) => {
266
266
  }
267
267
  return recastedUnionFromHomes(plan.source, plan.target, text, homes);
268
268
  }
269
+ case 'dispatch': {
270
+ // The same discriminant chain `taggedUnionArmText` spells, with the last
271
+ // arm untested: the plan admitted every arm, so one of them is live.
272
+ // Each home is spelled at the whole target's type so an optional target
273
+ // wraps once per arm and an absent arm is its empty state.
274
+ const targetType = cppTypeOf(plan.target);
275
+ const payload = plan.target.kind === 'optional' ? plan.target.payload : plan.target;
276
+ const homes = [];
277
+ for (const [index, arm] of plan.arms.entries()) {
278
+ const from = plan.source.arms[index];
279
+ if (from === undefined)
280
+ return null;
281
+ const armText = `${text}.get<${index}>()`;
282
+ const rendered = arm.via === 'exact'
283
+ ? armText
284
+ : arm.via === 'absent'
285
+ ? null
286
+ : arm.via === 'convert'
287
+ ? convertedValueText(from.value, payload, armText)
288
+ : recordViewText(ctx, arm.via, armText);
289
+ if (arm.via !== 'absent' && rendered === null)
290
+ return null;
291
+ homes.push(rendered === null ? `${targetType}()` : `${targetType}(${rendered})`);
292
+ }
293
+ let result = homes[homes.length - 1];
294
+ if (result === undefined)
295
+ return null;
296
+ for (let index = homes.length - 2; index >= 0; index--)
297
+ result = `${text}.is<${index}>() ? ${homes[index]} : (${result})`;
298
+ return `(${result})`;
299
+ }
269
300
  case 'fields':
270
301
  return recordFieldsViewText(ctx, plan, text);
271
302
  }
@@ -8,8 +8,8 @@ import type { CallableAbi } from '../../representation/model.js';
8
8
  import type { RepresentationDeriver } from '../../representation/derive.js';
9
9
  import type { CppArtifact } from './document.js';
10
10
  import type { HostSpellings } from './host/host-members.js';
11
- import { type CaptureIndex, type TemplateObjectDefinition } from './emit-context.js';
12
- import type { ConversionCensus } from '../../conversion/nodes.js';
11
+ import { type CaptureIndex, type CallableFactsSpelling, type TemplateObjectDefinition } from './emit-context.js';
12
+ import { type ConversionCensus } from '../../conversion/nodes.js';
13
13
  import { type PrinterDrift } from './emit-narrowing.js';
14
14
  import type { IntegerStorageFacts } from '../../ir/integers.js';
15
15
  import { type InstantiationFacts } from '../../ir/instantiation.js';
@@ -41,5 +41,5 @@ import type { HostMethodAlias } from './host/host-method-aliases.js';
41
41
  * the same namespace or an absent constant -- see the call site for why an
42
42
  * absent arm is the one thing that may accompany it.
43
43
  */
44
- export declare const emitBody: (body: IrBody, placements: ReadonlyMap<DeclarationId, BindingPlacement>, classes: ReadonlyMap<DeclarationId, ClassLayout>, hosts: HostSpellings, deriver: RepresentationDeriver, wellKnownSymbols: ReadonlyMap<DeclarationId, string> | undefined, captures: CaptureIndex | undefined, symbolKeys: Map<string, string>, templateObjects: Map<string, TemplateObjectDefinition>, directCallableBindings?: ReadonlyMap<DeclarationId, FunctionId>, virtualDispatch?: ReadonlyMap<string, CallableAbi>, narrowedStorage?: IntegerStorageFacts, narrowedFormals?: ReadonlySet<number>, repeatedConstructors?: ReadonlyMap<DeclarationId, DeclarationId>, dyingArguments?: ReadonlySet<IrValueId>, instantiation?: InstantiationFacts, abiOfCallable?: (callable: FunctionId) => IrBody["abi"], hostMethodAliases?: ReadonlyMap<DeclarationId, HostMethodAlias>, callableMemberCandidates?: ReadonlyMap<string, FunctionId>, borrowableMemberBodies?: ReadonlySet<FunctionId>, stableBorrowEntries?: ReadonlyMap<string, StableBorrowEntry>, printerDrift?: PrinterDrift[], conversions?: ConversionCensus | null, nativeSelections?: ReadonlyMap<string, NativeSelectionHelper> | undefined, callableIdentityDemand?: CallableIdentityDemand, nativeIntegrityRestricted?: boolean, fixedFieldStateConstant?: boolean) => readonly CppArtifact[];
44
+ export declare const emitBody: (body: IrBody, placements: ReadonlyMap<DeclarationId, BindingPlacement>, classes: ReadonlyMap<DeclarationId, ClassLayout>, hosts: HostSpellings, deriver: RepresentationDeriver, wellKnownSymbols: ReadonlyMap<DeclarationId, string> | undefined, captures: CaptureIndex | undefined, symbolKeys: Map<string, string>, templateObjects: Map<string, TemplateObjectDefinition>, directCallableBindings?: ReadonlyMap<DeclarationId, FunctionId>, virtualDispatch?: ReadonlyMap<string, CallableAbi>, narrowedStorage?: IntegerStorageFacts, narrowedFormals?: ReadonlySet<number>, repeatedConstructors?: ReadonlyMap<DeclarationId, DeclarationId>, dyingArguments?: ReadonlySet<IrValueId>, instantiation?: InstantiationFacts, abiOfCallable?: (callable: FunctionId) => IrBody["abi"], functionFacts?: ReadonlyMap<FunctionId, CallableFactsSpelling>, hostMethodAliases?: ReadonlyMap<DeclarationId, HostMethodAlias>, callableMemberCandidates?: ReadonlyMap<string, FunctionId>, borrowableMemberBodies?: ReadonlySet<FunctionId>, stableBorrowEntries?: ReadonlyMap<string, StableBorrowEntry>, printerDrift?: PrinterDrift[], conversions?: ConversionCensus | null, nativeSelections?: ReadonlyMap<string, NativeSelectionHelper> | undefined, callableIdentityDemand?: CallableIdentityDemand, nativeIntegrityRestricted?: boolean, fixedFieldStateConstant?: boolean) => readonly CppArtifact[];
45
45
  export { cppConstructedThunkName, cppConstructThunkName, cppFormalName, cppReceiverName, cppThunkName, isCppEmitBlockedError } from './emit-context.js';
@@ -7,7 +7,7 @@ import { constructedBaseOf } from '../../projection/classes.js';
7
7
  import { allOperationsOf } from '../../ir/model.js';
8
8
  import { resultOfIrOperation } from '../../ir/queries.js';
9
9
  import { representationKey } from '../../representation/model.js';
10
- import { cppFormalName, cppReceiverName, createCppEmitBlockedError, createEmitContext, defineValue, defineValueAlias, directCalleesOf, emptyCaptureIndex, isIntegerStorageValue, isCppEmitBlockedError, operandText, storageTypeOf, cppThunkName, sealFactFieldsForRender } from './emit-context.js';
10
+ import { cppFormalName, cppReceiverName, createCppEmitBlockedError, createEmitContext, defineValue, defineValueAlias, directCalleesOf, emptyCaptureIndex, isIntegerStorageValue, isCppEmitBlockedError, operandText, storageTypeOf, cppThunkEntryText, sealFactFieldsForRender } from './emit-context.js';
11
11
  import { hostMemberReadsOf } from './host/emit-host-properties.js';
12
12
  import { hostNamespaceReadsOf } from './host-namespace-reads.js';
13
13
  import { functionSourceReadsOf } from './function-source-reads.js';
@@ -18,6 +18,7 @@ import { reactiveOriginsOf } from './reactive-origins.js';
18
18
  import { renderTryRegion } from './emit-exceptions.js';
19
19
  import { emitReturn } from './emit-return.js';
20
20
  import { collectDirectBindingSinks, classObjectReadsOf, hostClassReadsOf, collectFormalCells, earlyCapturedCellPrologue, emitBindingRead, emitBindingWrite } from './emit-bindings.js';
21
+ import { EXACT_ARM_MATERIALIZER } from '../../conversion/nodes.js';
21
22
  import { alignedValueText, emitMergeLiveArmRebuild, namedConversionText, widenedStoreText } from './emit-narrowing.js';
22
23
  import { admitDenseWindows, collectCapacityHints, emitAllocateArrayObject, emitDenseSetup, emitFillLoop } from './emit-arrays.js';
23
24
  import { noInstantiationFacts } from '../../ir/instantiation.js';
@@ -888,7 +889,12 @@ const emitConvert = (ctx, lines, operation) => {
888
889
  // (`alignedValueText` asks the census for the same pair and renders the
889
890
  // same node; a pair the census refused is its drift row, and the chain's).
890
891
  const named = ctx.conversions.nodeById(operation.conversionUse);
891
- const text = (named?.capability.kind === 'coercion'
892
+ // A coercion and an exact-arm projection each share their (source, target)
893
+ // pair with the store node `alignedValueText` would look up, so for those
894
+ // the instruction's own node is the only thing that says which one runs.
895
+ const namedOverPair = named?.capability.kind === 'coercion' ||
896
+ (named?.capability.kind === 'static' && named.capability.materializer.id === EXACT_ARM_MATERIALIZER);
897
+ const text = (namedOverPair
892
898
  ? namedConversionText(ctx, 'emit.ts:1026', named, sourceText)
893
899
  : alignedValueText(ctx, 'emit.ts:1026', operation.source.representation, operation.result.representation, sourceText)) ??
894
900
  // A method value escaping into a receiver-less slot: the receiver is
@@ -1456,7 +1462,7 @@ const settleDeadCalleeSideEffects = (ctx, operation) => {
1456
1462
  // updating.
1457
1463
  const carrier = operation.result.representation;
1458
1464
  if (carrier.kind === 'function-value-dispatch' && carrier.abi.parameters.length === 0 && carrier.abi.receiver === null) {
1459
- ctx.deferredTexts.set(operation.result.id, `${cppTypeOf(carrier)}{&${cppThunkName(operation.functionId)}, nullptr}`);
1465
+ ctx.deferredTexts.set(operation.result.id, `${cppTypeOf(carrier)}{${cppThunkEntryText(ctx, operation.functionId)}, nullptr}`);
1460
1466
  }
1461
1467
  }
1462
1468
  return;
@@ -1618,7 +1624,7 @@ export const emitBody = (body, placements, classes, hosts, deriver, wellKnownSym
1618
1624
  symbolKeys, templateObjects, directCallableBindings = new Map(),
1619
1625
  // `virtualDispatch` is every `class key` with a dispatch member (`virtual-methods.ts`);
1620
1626
  // `narrowedStorage`/`narrowedFormals` are what the integer census settled here.
1621
- virtualDispatch = new Map(), narrowedStorage = { reads: new Map(), integral: new Set(), magnitudes: new Map() }, narrowedFormals = new Set(), repeatedConstructors = new Map(), dyingArguments = new Set(), instantiation = noInstantiationFacts, abiOfCallable = () => null, hostMethodAliases = new Map(), callableMemberCandidates = new Map(), borrowableMemberBodies = new Set(), stableBorrowEntries = new Map(), printerDrift = [], conversions = null, nativeSelections = undefined, callableIdentityDemand = observesEveryCallableIdentity, nativeIntegrityRestricted = true, fixedFieldStateConstant = false) => {
1627
+ virtualDispatch = new Map(), narrowedStorage = { reads: new Map(), integral: new Set(), magnitudes: new Map() }, narrowedFormals = new Set(), repeatedConstructors = new Map(), dyingArguments = new Set(), instantiation = noInstantiationFacts, abiOfCallable = () => null, functionFacts = new Map(), hostMethodAliases = new Map(), callableMemberCandidates = new Map(), borrowableMemberBodies = new Set(), stableBorrowEntries = new Map(), printerDrift = [], conversions = null, nativeSelections = undefined, callableIdentityDemand = observesEveryCallableIdentity, nativeIntegrityRestricted = true, fixedFieldStateConstant = false) => {
1622
1628
  // Every fact this body settles before a single line renders, computed here
1623
1629
  // -- from `body` and the plain, already-available inputs above -- and
1624
1630
  // handed to `createEmitContext` as one argument instead of mutated onto the
@@ -1707,7 +1713,7 @@ virtualDispatch = new Map(), narrowedStorage = { reads: new Map(), integral: new
1707
1713
  directCallees: directCalleeFacts.directCallees,
1708
1714
  directCalleeAbis: directCalleeFacts.directCalleeAbis
1709
1715
  };
1710
- const { ctx, prepass } = createEmitContext(body.abi, body.sourceOwner, placements, classes, hosts, deriver, bodyFacts, wellKnownSymbols, captures, symbolKeys, templateObjects, directCallableBindings, virtualDispatch, narrowedFormals, repeatedConstructors, dyingArguments, instantiation, abiOfCallable, hostMethodAliases, callableMemberCandidates, borrowableMemberBodies, stableBorrowEntries, printerDrift, conversions, nativeSelections, callableIdentityDemand, nativeIntegrityRestricted, fixedFieldStateConstant);
1716
+ const { ctx, prepass } = createEmitContext(body.abi, body.sourceOwner, placements, classes, hosts, deriver, bodyFacts, wellKnownSymbols, captures, symbolKeys, templateObjects, directCallableBindings, virtualDispatch, narrowedFormals, repeatedConstructors, dyingArguments, instantiation, abiOfCallable, functionFacts, hostMethodAliases, callableMemberCandidates, borrowableMemberBodies, stableBorrowEntries, printerDrift, conversions, nativeSelections, callableIdentityDemand, nativeIntegrityRestricted, fixedFieldStateConstant);
1711
1717
  // `ownedValues` stays a genuine render-time OUTPUT buffer (`EmitContext`'s
1712
1718
  // own doc: `defineValue` grows it as each operation's result is named) --
1713
1719
  // unlike `ownedDyingValues` above, a formal argument's membership in it is
@@ -27,7 +27,7 @@ import { classesConstructedUnobservably, functionsIgnoringTheirReceiver } from '
27
27
  import { renderJsonStructDeclarations } from './emit-json.js';
28
28
  import { alignedValueText } from './emit-narrowing.js';
29
29
  import { recordLayoutPolicyOf } from '../../projection/fields.js';
30
- import { cppAbiParameterType, cppAbiType, cppStringLiteral, cppStringViewLiteral, cppBodyName, cppCallableDeclarationTagName, cppCommonJsModuleName, cppCommonJsRecordName, cppNarrowedIntegerType, cppRefcountedReceiver, cppBoxedType, cppClassName, cppConstructName, cppGlobalName, cppInitializeName, cppRecordFieldName, cppRecordStructName, cppResultTypeOf, cppTypeOf, sanitizeForCppIdentifier } from './types.js';
30
+ import { cppAbiParameterType, cppAbiType, cppStringLiteral, cppBodyName, cppCallableDeclarationTagName, cppCommonJsModuleName, cppCommonJsRecordName, cppNarrowedIntegerType, cppRefcountedReceiver, cppBoxedType, cppClassName, cppConstructName, cppGlobalName, cppInitializeName, cppRecordFieldName, cppRecordStructName, cppResultTypeOf, cppTypeOf, sanitizeForCppIdentifier } from './types.js';
31
31
  import { buildHostMethodAliasIndex } from './host/host-method-aliases.js';
32
32
  import { cppRecursiveContainerDeclarations, cppRecursiveContainerTraceEdges } from './recursive-containers.js';
33
33
  /**
@@ -562,7 +562,7 @@ const signatureOf = (body, captures, narrowed = new Set(), narrowedResult = fals
562
562
  const formals = bodyFormalsOf(body.sourceOwner, effectiveAbi, admission, narrowed, borrowsReceiver(body), borrowed);
563
563
  return `${result} ${name}(${formals.join(', ')})`;
564
564
  };
565
- const thunkOf = (body, captures, narrowed, narrowedResult, linkage, preserveFunctionFacts) => {
565
+ const thunkOf = (body, captures, narrowed, narrowedResult, linkage) => {
566
566
  if (!body.abi)
567
567
  return null;
568
568
  const abi = body.abi;
@@ -601,17 +601,11 @@ const thunkOf = (body, captures, narrowed, narrowedResult, linkage, preserveFunc
601
601
  const construct = constructThunkOf(body, abi, hasEnvironment, linkage);
602
602
  return {
603
603
  prototype: [`${signature};`, ...(construct === null ? [] : [construct.prototype])].join('\n'),
604
- definition: [
605
- `${signature} { ${statement} }`,
606
- ...(!preserveFunctionFacts || body.functionSource === undefined
607
- ? []
608
- : [
609
- `[[maybe_unused]] static const bool ${cppThunkName(body.sourceOwner)}_source = ` +
610
- `gea::CallableObject<${cppAbiType(abi)}>::registerSource<&${cppThunkName(body.sourceOwner)}>(` +
611
- `${cppStringViewLiteral(body.functionName ?? '')}, ${body.functionLength ?? 0}, ${cppStringViewLiteral(body.functionSource)});`
612
- ]),
613
- ...(construct === null ? [] : [construct.definition])
614
- ].join('\n')
604
+ // No `registerSource` beside the thunk any more: the function's
605
+ // `name`/`length`/source text register at the sites that mint a function
606
+ // object from it (`emit-context.ts`'s `cppThunkEntryText`), so a function
607
+ // nothing reaches is not pinned into the binary by its own registration.
608
+ definition: [`${signature} { ${statement} }`, ...(construct === null ? [] : [construct.definition])].join('\n')
615
609
  };
616
610
  };
617
611
  /**
@@ -1639,9 +1633,24 @@ export const renderTranslationUnit = (input) => {
1639
1633
  }
1640
1634
  return false;
1641
1635
  });
1636
+ // The facts every mint site of a function registers (`cppThunkEntryText`),
1637
+ // spelled once per body here rather than looked up through the thunk. Empty
1638
+ // when the census above found no reader, so `&thunk` alone is stored.
1639
+ const functionFacts = new Map();
1640
+ if (preserveFunctionFacts)
1641
+ for (const body of input.bodies) {
1642
+ if (isRegionId(body.sourceOwner) || body.abi === null || body.functionSource === undefined)
1643
+ continue;
1644
+ functionFacts.set(body.sourceOwner, {
1645
+ abiType: cppAbiType(body.abi),
1646
+ name: body.functionName ?? '',
1647
+ length: body.functionLength ?? 0,
1648
+ source: body.functionSource
1649
+ });
1650
+ }
1642
1651
  const thunks = new Map();
1643
1652
  for (const body of input.bodies) {
1644
- const thunk = thunkOf(body, captures, formalsNarrowedIn(body.sourceOwner), resultNarrowedIn(body.sourceOwner), linkage, preserveFunctionFacts);
1653
+ const thunk = thunkOf(body, captures, formalsNarrowedIn(body.sourceOwner), resultNarrowedIn(body.sourceOwner), linkage);
1645
1654
  if (thunk)
1646
1655
  thunks.set(body, thunk);
1647
1656
  }
@@ -1713,7 +1722,7 @@ export const renderTranslationUnit = (input) => {
1713
1722
  continue;
1714
1723
  }
1715
1724
  try {
1716
- const sections = emitBody(body, input.placements, input.classes, hosts, deriver, input.wellKnownSymbols, captures, symbolKeys, templateObjects, directCallables, virtuals.dispatched, narrowedStorage.factsOf(body.sourceOwner), formalsNarrowedIn(body.sourceOwner), repeatedConstructors, dyingArguments, instantiation, (callable) => abiByBody.get(String(callable)) ?? null, hostMethodAliases, callableMemberCandidates, borrowableMemberBodies, stableBorrowEntries, printerDrift, input.conversions, selectionHelpers, callableIdentityDemand, nativeIntegrityRestricted, fixedFieldStateConstant);
1725
+ const sections = emitBody(body, input.placements, input.classes, hosts, deriver, input.wellKnownSymbols, captures, symbolKeys, templateObjects, directCallables, virtuals.dispatched, narrowedStorage.factsOf(body.sourceOwner), formalsNarrowedIn(body.sourceOwner), repeatedConstructors, dyingArguments, instantiation, (callable) => abiByBody.get(String(callable)) ?? null, functionFacts, hostMethodAliases, callableMemberCandidates, borrowableMemberBodies, stableBorrowEntries, printerDrift, input.conversions, selectionHelpers, callableIdentityDemand, nativeIntegrityRestricted, fixedFieldStateConstant);
1717
1726
  const stableEntry = stableBorrowEntries.get(cppBodyName(body.sourceOwner));
1718
1727
  const commonJsScope = commonJsOwner === null || commonJsOwner.nativeRecord
1719
1728
  ? []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geastack/compiler",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "The geastack TypeScript-to-C++ compiler: one semantic spine, no source-shaped authority, no boxing of statically typed values.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {