@geastack/compiler 1.0.15 → 1.0.16
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.
- package/dist/ir/integers.js +24 -2
- package/dist/ir/transfer.js +17 -8
- package/dist/semantics/frontend.js +4 -1
- package/dist/semantics/normalize/flow/callable-reach.js +1 -3
- package/dist/semantics/normalize/global-host-mutations.js +2 -2
- package/dist/semantics/normalize/reachability.d.ts +20 -5
- package/dist/semantics/normalize/reachability.js +119 -14
- package/dist/semantics/normalize/specialization.js +1 -4
- package/dist/targets/cpp/class-layout.d.ts +40 -0
- package/dist/targets/cpp/class-layout.js +112 -0
- package/dist/targets/cpp/emit-callable.js +2 -1
- package/dist/targets/cpp/emit-narrowing.js +7 -0
- package/dist/targets/cpp/emit-properties.js +13 -3
- package/dist/targets/cpp/prototype/emit-prototype-regexp.d.ts +1 -1
- package/dist/targets/cpp/prototype/emit-prototype-regexp.js +58 -2
- package/dist/targets/cpp/translation-unit.js +8 -1
- package/package.json +5 -5
- package/src/targets/cpp/runtime/gea_dynamic_proxy.h +38 -0
- package/src/targets/cpp/runtime/gea_runtime.h +124 -1
package/dist/ir/integers.js
CHANGED
|
@@ -240,8 +240,30 @@ export const narrowableIntegersOf = (body, storage = noStorageFacts) => {
|
|
|
240
240
|
return true;
|
|
241
241
|
if (compute.form === 'update')
|
|
242
242
|
return integral.has(left.value);
|
|
243
|
-
if (compute.form === 'unary')
|
|
244
|
-
|
|
243
|
+
if (compute.form === 'unary') {
|
|
244
|
+
if (compute.operator !== '-' && compute.operator !== '+')
|
|
245
|
+
return false;
|
|
246
|
+
// Negating a zero this census can SEE is the one negation whose result a
|
|
247
|
+
// 64-bit integer cannot carry. `-0` is an integer by every arithmetic
|
|
248
|
+
// test -- `Number.isSafeInteger(-0)` is true and `-0 === 0` -- so nothing
|
|
249
|
+
// else here strikes it; but two's complement has a single zero, and the
|
|
250
|
+
// sign is gone at the store. ECMA-262 can read that sign back
|
|
251
|
+
// (`Object.is(x, -0)` is true where `Object.is(x, 0)` is false, and
|
|
252
|
+
// `1 / -0` is `-Infinity`), so a slot narrowed on the strength of a `-0`
|
|
253
|
+
// answers both the other way with no diagnostic. `emit.ts` already
|
|
254
|
+
// spells this operand `-0.0` for exactly that reason; the value it
|
|
255
|
+
// carefully produces is then rounded away by the storage this census
|
|
256
|
+
// hands out.
|
|
257
|
+
//
|
|
258
|
+
// Only the statically visible one is refused. A `-0` that arises at
|
|
259
|
+
// runtime -- `x * -1`, `-1 % 1`, `x - x` never, but `x * y` with either
|
|
260
|
+
// zero -- is the same class of hazard as an overflowing sum, which this
|
|
261
|
+
// design already tolerates: refusing every product and remainder that
|
|
262
|
+
// COULD be a negative zero would narrow nothing at all.
|
|
263
|
+
if (compute.operator === '-' && constants.get(left.value) === 0)
|
|
264
|
+
return false;
|
|
265
|
+
return integral.has(left.value);
|
|
266
|
+
}
|
|
245
267
|
if (compute.form !== 'binary' || !right)
|
|
246
268
|
return false;
|
|
247
269
|
if (compute.operator === '%') {
|
package/dist/ir/transfer.js
CHANGED
|
@@ -137,7 +137,6 @@ export const ownedDyingValuesOf = (body) => {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
const graph = controlFlowGraphOf(body);
|
|
140
|
-
const cyclic = cyclicBlocksOf(body);
|
|
141
140
|
const dying = new Set();
|
|
142
141
|
for (const [value, definition] of definitions) {
|
|
143
142
|
const readers = uses.get(value);
|
|
@@ -146,17 +145,27 @@ export const ownedDyingValuesOf = (body) => {
|
|
|
146
145
|
const use = readers[0];
|
|
147
146
|
if (use === undefined)
|
|
148
147
|
continue;
|
|
149
|
-
//
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
148
|
+
// Defined and consumed in ONE block: the definition dominates the use, so
|
|
149
|
+
// every execution of the block writes the storage before the use reads it.
|
|
150
|
+
// A loop re-entering the block re-runs the definition first, so the move
|
|
151
|
+
// can never be observed by a later iteration -- the same argument
|
|
152
|
+
// `buildDyingArgumentIndex` makes for a value a block defines and passes.
|
|
153
|
+
// This is what a `for`/`for-in` body is made of: `b = read(k)` inside a
|
|
154
|
+
// loop copied a whole string or union per iteration because its block was
|
|
155
|
+
// cyclic, although nothing could ever read the temporary again.
|
|
156
156
|
if (use === definition) {
|
|
157
157
|
dying.add(value);
|
|
158
158
|
continue;
|
|
159
159
|
}
|
|
160
|
+
// A value defined outside a loop can still have its sole SSA use inside
|
|
161
|
+
// the loop. Moving at that use empties the storage on the first iteration
|
|
162
|
+
// and every later iteration observes a null Ref. That is exactly what the
|
|
163
|
+
// re-entry search below decides: it walks forward from the use with the
|
|
164
|
+
// DEFINITION block removed from the graph, so it finds the use again only
|
|
165
|
+
// when some path re-enters it without redefining the value first. A use in
|
|
166
|
+
// a loop whose every trip runs the definition (`k = cursor.next()` in one
|
|
167
|
+
// block, `key = k` in the next) is not re-entered by that measure, and a
|
|
168
|
+
// use in a loop the definition sits outside of is.
|
|
160
169
|
// Exceptional region edges are not represented by this CFG.
|
|
161
170
|
if (body.tryRegions.length > 0 || (body.iteratorCloseRegions?.length ?? 0) > 0)
|
|
162
171
|
continue;
|
|
@@ -44,7 +44,7 @@ import { censusRefusalCounts } from './normalize/census-refusal.js';
|
|
|
44
44
|
import { createCellPolicyRegistry, defaultCellEvidencePolicies, buildCellFactsTable, publishCellFacts } from './normalize/cells/index.js';
|
|
45
45
|
import { settleBindingCensus } from './normalize/binding-fixpoint.js';
|
|
46
46
|
import { withJsDocTypeNames } from './normalize/jsdoc-type-names.js';
|
|
47
|
-
import { censusReachability, moduleEvaluationOrder, nodeIsReachable } from './normalize/reachability.js';
|
|
47
|
+
import { censusReachability, fileEvaluates, moduleEvaluationOrder, nodeIsReachable } from './normalize/reachability.js';
|
|
48
48
|
import { createProgram, defaultCompilerOptions } from './program.js';
|
|
49
49
|
import { createFrontendTiming } from './frontend-timing.js';
|
|
50
50
|
import { classHeritageOf } from './class-heritage.js';
|
|
@@ -1105,7 +1105,10 @@ export const runFrontend = (input) => {
|
|
|
1105
1105
|
// reachability walk pruned whole, or one whose top level censused nothing,
|
|
1106
1106
|
// has no region -- and a name in this list that no region answers would be
|
|
1107
1107
|
// a call to a function nothing defines.
|
|
1108
|
+
// `fileEvaluates` is the reached-statement half of that: a file kept only
|
|
1109
|
+
// for a layout-only class has a region and, by design, no body.
|
|
1108
1110
|
moduleOrder: moduleEvaluationOrder({ checker: compiled.checker, files: compiled.sourceFiles, entries: compiled.entryFiles })
|
|
1111
|
+
.filter((file) => fileEvaluates(reachable, file))
|
|
1109
1112
|
.map((file) => regionId(identities.nodeIdOf(file), 'module-body'))
|
|
1110
1113
|
.filter((region) => normalized.graph.regions.has(region)),
|
|
1111
1114
|
sourceFileNames: identities.sourceFileNames,
|
|
@@ -5146,9 +5146,7 @@ const closedMemberCallableUses = (checker, flow, member, countedCalls, givenRece
|
|
|
5146
5146
|
const origins = key === null ? null : allocationOriginsOf(receiver);
|
|
5147
5147
|
traceClosedCallee(call, 'invocationTargetsOf/origins', `key=${key ?? 'null'} origins=${origins === null ? 'null' : origins.classes.size}`);
|
|
5148
5148
|
if (origins !== null) {
|
|
5149
|
-
const privateDeclarations = ts.isPropertyAccessExpression(callee) && ts.isPrivateIdentifier(callee.name)
|
|
5150
|
-
? privateMemberDeclarationsOf(callee.name)
|
|
5151
|
-
: null;
|
|
5149
|
+
const privateDeclarations = ts.isPropertyAccessExpression(callee) && ts.isPrivateIdentifier(callee.name) ? privateMemberDeclarationsOf(callee.name) : null;
|
|
5152
5150
|
const selected = new Set();
|
|
5153
5151
|
for (const owner of origins.classes) {
|
|
5154
5152
|
const slotDeclarations = privateDeclarations ??
|
|
@@ -4416,7 +4416,7 @@ computedKeysOf = () => null, trustSeed = initialCensusSeed) => {
|
|
|
4416
4416
|
// value.
|
|
4417
4417
|
if ((ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent)) && parent.propertyName === node)
|
|
4418
4418
|
return true;
|
|
4419
|
-
return ((
|
|
4419
|
+
return ((ts.isVariableDeclaration(parent) ||
|
|
4420
4420
|
ts.isImportSpecifier(parent) ||
|
|
4421
4421
|
ts.isExportSpecifier(parent) ||
|
|
4422
4422
|
ts.isImportClause(parent) ||
|
|
@@ -4441,7 +4441,7 @@ computedKeysOf = () => null, trustSeed = initialCensusSeed) => {
|
|
|
4441
4441
|
ts.isPropertyAssignment(parent) ||
|
|
4442
4442
|
ts.isShorthandPropertyAssignment(parent) ||
|
|
4443
4443
|
ts.isTypeParameterDeclaration(parent)) &&
|
|
4444
|
-
parent.name === node)
|
|
4444
|
+
parent.name === node);
|
|
4445
4445
|
};
|
|
4446
4446
|
let globalEscapeAnswer = null;
|
|
4447
4447
|
const globalObjectEscapesIntoData = () => {
|
|
@@ -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
|
|
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
|
|
48
|
-
*
|
|
49
|
-
*
|
|
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
|
-
|
|
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) &&
|
|
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.
|
|
@@ -523,7 +523,8 @@ const emitLazyArrowFieldCall = (ctx, lines, operation) => {
|
|
|
523
523
|
// conversion below then adapts the stored result to the read's, or refuses
|
|
524
524
|
// and declines the fusion.
|
|
525
525
|
const storedField = ctx.classes.get(site.owner)?.fields.find((field) => field.key === fieldName)?.representation ?? null;
|
|
526
|
-
const storedAbi = storedField !== null &&
|
|
526
|
+
const storedAbi = storedField !== null &&
|
|
527
|
+
(storedField.kind === 'function' || storedField.kind === 'function-family' || storedField.kind === 'function-value-dispatch')
|
|
527
528
|
? storedField.abi
|
|
528
529
|
: callee.abi;
|
|
529
530
|
const bodyAbi = ctx.abiOfCallable(plan.body);
|
|
@@ -3828,6 +3828,13 @@ const ownsItsStorage = (carrier) => {
|
|
|
3828
3828
|
return carrier.ownership === 'shared-refcount';
|
|
3829
3829
|
if (carrier.kind === 'optional')
|
|
3830
3830
|
return ownsItsStorage(carrier.payload);
|
|
3831
|
+
// A sum owns whatever its live arm owns. `gea::TaggedUnion` has a real move
|
|
3832
|
+
// constructor (`TaggedUnionOps::moveConstruct`), so a dying union hands its
|
|
3833
|
+
// string or reference over instead of `copyConstruct`ing it and then
|
|
3834
|
+
// destroying the original -- per header value, per request, in node-compat's
|
|
3835
|
+
// `writeHead`, where this was three heap copies of one content-type string.
|
|
3836
|
+
if (carrier.kind === 'tagged-union')
|
|
3837
|
+
return carrier.arms.some((arm) => ownsItsStorage(arm.value));
|
|
3831
3838
|
return false;
|
|
3832
3839
|
};
|
|
3833
3840
|
/**
|
|
@@ -592,7 +592,7 @@ export const emitGet = (ctx, lines, operation) => {
|
|
|
592
592
|
// struct-member fallthrough at the end would take `re.test` and emit
|
|
593
593
|
// `re->test`, which is a clang error naming a member instead of a refusal
|
|
594
594
|
// naming the method.
|
|
595
|
-
const regexpMember = regexpMemberText(ctx, operation.receiver, operation.key, operation.result.id, operation.result.representation, (fieldName, storage) => narrowedFieldReadText(ctx, operation, fieldName, storage)) ?? stringObjectMemberText(ctx, operation.receiver, operation.key, operation.result.representation);
|
|
595
|
+
const regexpMember = regexpMemberText(ctx, operation.receiver, operation.key, operation.result.id, operation.result.representation, (fieldName, storage, declared) => narrowedFieldReadText(ctx, operation, fieldName, storage, null, declared)) ?? stringObjectMemberText(ctx, operation.receiver, operation.key, operation.result.representation);
|
|
596
596
|
if (regexpMember !== null) {
|
|
597
597
|
// An empty rendering is a deferred RegExp.prototype method read -- see
|
|
598
598
|
// the identical comment on the array-access branch above.
|
|
@@ -911,8 +911,18 @@ const fixedFieldPresenceText = (ctx, receiver, fieldName) => {
|
|
|
911
911
|
* the original defect got in. A disagreement it declines to convert is a
|
|
912
912
|
* defect upstream, not a spelling this may invent, so that refuses by name.
|
|
913
913
|
*/
|
|
914
|
-
const narrowedFieldReadText = (ctx, operation, fieldName, storage, presence = null
|
|
915
|
-
|
|
914
|
+
const narrowedFieldReadText = (ctx, operation, fieldName, storage, presence = null,
|
|
915
|
+
/**
|
|
916
|
+
* The member's physical carrier, supplied by a caller that HAS it where the
|
|
917
|
+
* lookup below cannot. A compiler-owned native layout
|
|
918
|
+
* (`ExecResult`/`MatchResult`) never gets a sealed record layout, so
|
|
919
|
+
* `declaredFieldRepresentation` answers null for its members and this
|
|
920
|
+
* function would hand back the bare load -- correct until the read is
|
|
921
|
+
* narrowed, and a type error the moment it is. The caller that knows the
|
|
922
|
+
* struct says so instead of this function guessing.
|
|
923
|
+
*/
|
|
924
|
+
declaredStorage = null) => {
|
|
925
|
+
const declared = declaredStorage ?? declaredFieldRepresentation(ctx, operation.receiver, fieldName);
|
|
916
926
|
if (declared === null)
|
|
917
927
|
return storage;
|
|
918
928
|
const published = operation.result.representation;
|
|
@@ -72,7 +72,7 @@ export declare const regExpConstructionFromObject: (ctx: EmitContext, argument:
|
|
|
72
72
|
* Stated once; `regexpMemberText` and the prototype-read walk both ask it.
|
|
73
73
|
*/
|
|
74
74
|
export declare const deferredRegexpMethodClaim: (staticKeyTexts: ReadonlyMap<IrValueId, string>, receiver: IrOperand, key: IrOperand) => PrototypeMethodRead | null;
|
|
75
|
-
export declare const regexpMemberText: (ctx: EmitContext, receiver: IrOperand, key: IrOperand, result: IrValueId | null, resultRepresentation: Representation | undefined, narrow: (fieldName: string, storage: string) => string) => string | null;
|
|
75
|
+
export declare const regexpMemberText: (ctx: EmitContext, receiver: IrOperand, key: IrOperand, result: IrValueId | null, resultRepresentation: Representation | undefined, narrow: (fieldName: string, storage: string, declared: Representation | null) => string) => string | null;
|
|
76
76
|
/**
|
|
77
77
|
* `regexpStoreRefusal`'s whole decision, spelled as three outcomes rather than
|
|
78
78
|
* collapsed into "throws or doesn't":
|
|
@@ -216,6 +216,57 @@ const patternMethods = new Set(['test', 'exec', 'toString']);
|
|
|
216
216
|
* report group positions for non-participating groups at all.
|
|
217
217
|
*/
|
|
218
218
|
const resultDataMembers = new Set(['index', 'input', 'length', 'groups']);
|
|
219
|
+
/**
|
|
220
|
+
* What each of those members PHYSICALLY holds, per role.
|
|
221
|
+
*
|
|
222
|
+
* Stated here because nothing else can state it. These two structs are
|
|
223
|
+
* compiler-owned native layouts: `representation/derive.ts` deliberately does
|
|
224
|
+
* NOT seal a record layout for `RegExpExecArray`/`RegExpMatchArray` -- see its
|
|
225
|
+
* own comment on why sealing "a plausible-looking struct" for them would emit
|
|
226
|
+
* a program that compiles and is not a regular expression -- so
|
|
227
|
+
* `recordFieldsOfShape` answers nothing for their shape id and the generic
|
|
228
|
+
* `narrowedFieldReadText` has no declared carrier to reconcile against. It
|
|
229
|
+
* therefore returned the bare member load for every read, which is right only
|
|
230
|
+
* while the read is NOT narrowed.
|
|
231
|
+
*
|
|
232
|
+
* It is wrong the moment it is. `if (m.groups !== undefined) m.groups['id']`
|
|
233
|
+
* publishes the payload while the field stores the optional, and the bare load
|
|
234
|
+
* emitted `Optional<Ref<Dictionary<std::string>>>` where a
|
|
235
|
+
* `Ref<Dictionary<std::string>>` was wanted -- `->has` then resolved on
|
|
236
|
+
* `gea::Ref`, which has no such member, and every program that read a named
|
|
237
|
+
* capture group by key failed to compile (node-compat's `apps/http-parity`
|
|
238
|
+
* router was the first). The narrowing obligation the `narrow` parameter
|
|
239
|
+
* documents was being honoured at the call and dropped inside it, for want of
|
|
240
|
+
* this table.
|
|
241
|
+
*
|
|
242
|
+
* The three optionals are the two interfaces' own `?`, and they differ by
|
|
243
|
+
* role on purpose: `RegExpExecArray` declares `index`/`input` REQUIRED and
|
|
244
|
+
* `RegExpMatchArray` declares them optional, because a global pattern's match
|
|
245
|
+
* really does answer an array with neither. `length` is `double` on an exec
|
|
246
|
+
* result and the inherited `ArrayObject::length()` on a match result; neither
|
|
247
|
+
* is optional. Keep this in step with `ExecResult`/`MatchResult` in
|
|
248
|
+
* `runtime/gea_runtime.h` -- they are the same two facts, and clang checks
|
|
249
|
+
* only one of them.
|
|
250
|
+
*/
|
|
251
|
+
const namedGroupsStorage = {
|
|
252
|
+
kind: 'optional',
|
|
253
|
+
payload: { kind: 'dictionary', key: 'string', value: { kind: 'string' }, ownership: 'shared-refcount' },
|
|
254
|
+
absence: 'undefined'
|
|
255
|
+
};
|
|
256
|
+
const numberStorage = { kind: 'scalar', domain: 'number' };
|
|
257
|
+
const stringStorage = { kind: 'string' };
|
|
258
|
+
const optionalOf = (payload) => ({ kind: 'optional', payload, absence: 'undefined' });
|
|
259
|
+
const resultDataMemberStorage = (role, key) => {
|
|
260
|
+
if (key === 'groups')
|
|
261
|
+
return namedGroupsStorage;
|
|
262
|
+
if (key === 'length')
|
|
263
|
+
return numberStorage;
|
|
264
|
+
if (key === 'index')
|
|
265
|
+
return role === 'match-result' ? optionalOf(numberStorage) : numberStorage;
|
|
266
|
+
if (key === 'input')
|
|
267
|
+
return role === 'match-result' ? optionalOf(stringStorage) : stringStorage;
|
|
268
|
+
return null;
|
|
269
|
+
};
|
|
219
270
|
/** Whether `text` is a canonical array index -- a run of digits with no sign, point, or leading zero. */
|
|
220
271
|
const canonicalCaptureSlot = (text) => {
|
|
221
272
|
if (text.length === 0)
|
|
@@ -318,6 +369,11 @@ export const regexpMemberText = (ctx, receiver, key, result, resultRepresentatio
|
|
|
318
369
|
// fails to assign. The capture reads below do NOT go through it: they choose
|
|
319
370
|
// between `capture` and `capturedOrAbsent` on the published carrier already,
|
|
320
371
|
// and are not fields at all.
|
|
372
|
+
//
|
|
373
|
+
// `declared` is what the member physically holds. The caller cannot look it
|
|
374
|
+
// up for these two receivers -- their layout is never sealed -- so this
|
|
375
|
+
// supplies it from `resultDataMemberStorage`, and passing `null` (a RegExp
|
|
376
|
+
// pattern member) leaves the caller on its own lookup exactly as before.
|
|
321
377
|
narrow) => {
|
|
322
378
|
const role = regexpRoleOf(receiver.representation);
|
|
323
379
|
if (role === null)
|
|
@@ -349,7 +405,7 @@ narrow) => {
|
|
|
349
405
|
return unboxedReadText(resultRepresentation ?? { kind: 'dynamic', reason: 'declared-any-never-narrowed' }, `gea::runtime::regex::dynamicGet(${receiverText}, ${propertyKeyText(ctx, key, site)})`, site);
|
|
350
406
|
}
|
|
351
407
|
if (patternDataMembers.has(staticKey))
|
|
352
|
-
return narrow(staticKey, `${receiverText}->${staticKey}
|
|
408
|
+
return narrow(staticKey, `${receiverText}->${staticKey}`, null);
|
|
353
409
|
if (deferredRegexpMethodClaim(ctx.staticKeyTexts, receiver, key) !== null) {
|
|
354
410
|
if (result === null) {
|
|
355
411
|
throw createCppEmitBlockedError(`host-invocation:RegExp.prototype.${staticKey}`, `"${staticKey}" is a RegExp.prototype method, and this access publishes no value for its call to consume`);
|
|
@@ -369,7 +425,7 @@ narrow) => {
|
|
|
369
425
|
// `MatchResult`; one spelling for both compiled the exec result's read as
|
|
370
426
|
// a call on a `double`.
|
|
371
427
|
const member = staticKey === 'length' && role === 'match-result' ? 'length()' : staticKey;
|
|
372
|
-
return narrow(staticKey, `${receiverText}->${member}
|
|
428
|
+
return narrow(staticKey, `${receiverText}->${member}`, resultDataMemberStorage(role, staticKey));
|
|
373
429
|
}
|
|
374
430
|
if (canonicalCaptureSlot(staticKey))
|
|
375
431
|
return captureReadText(role, receiverText, staticKey, resultRepresentation);
|
|
@@ -17,7 +17,7 @@ import { createCppDocumentBuilder, emptyCppFacts, render, spliceRendered } from
|
|
|
17
17
|
import { beginUnionAliasing, endUnionAliasing } from './types.js';
|
|
18
18
|
import { cppConstructedThunkName, cppConstructThunkName, cppFormalName, cppReceiverName, cppThunkName, emitBody, isCppEmitBlockedError } from './emit.js';
|
|
19
19
|
import { cppCaptureFieldName, cppCaptureReceiverFieldName, cppEnvironmentLocalName, cppEnvironmentParamName, cppEnvironmentSlotName, cppEnvironmentStructName, effectiveAbiOf, symbolKeyDefinitions, templateObjectDefinitions } from './emit-context.js';
|
|
20
|
-
import { censusClassStaticFieldStorage, censusLazyArrowFields, classBoxable, cppFieldInitializerStatements, publishBoxableClasses, publishClassStaticFieldStorage, publishLazyArrowFieldPlans } from './class-layout.js';
|
|
20
|
+
import { censusClassStaticFieldStorage, censusConstantFieldInitializers, censusLazyArrowFields, classBoxable, cppFieldInitializerStatements, publishBoxableClasses, publishClassStaticFieldStorage, publishConstantFieldInitializers, publishLazyArrowFieldPlans } from './class-layout.js';
|
|
21
21
|
import { classStaticFieldStorageRows, cppRecordDeclarations, cppStructNameOf, declaredFieldRepresentationOf, declaredRecordFieldOf, recordAccessorBodiesOf, representationNamesMintedStruct, withUnreadParametersUnnamed } from './records.js';
|
|
22
22
|
import { prototypeReadHooks, virtualMethodEmission, virtualMethodFamiliesOf } from './virtual-methods.js';
|
|
23
23
|
import { virtualDispatchVerdictOf } from '../../projection/dispatch.js';
|
|
@@ -1395,6 +1395,13 @@ export const renderTranslationUnit = (input) => {
|
|
|
1395
1395
|
// actual reader in this same function -- `cppRecordDeclarations` itself --
|
|
1396
1396
|
// so it cannot be published after, the way that one is.
|
|
1397
1397
|
publishLazyArrowFieldPlans(input.classes, censusLazyArrowFields(input.bodies, input.classes, captures));
|
|
1398
|
+
// Published alongside it, and read by the same constructor emission: which
|
|
1399
|
+
// field initializers are a single constant, so a store that writes what
|
|
1400
|
+
// value-initialization already wrote can be dropped. Passed the plugin's own
|
|
1401
|
+
// `reactive.fields` rather than the settled `celledFields`, which
|
|
1402
|
+
// `cppRecordDeclarations` has not produced yet -- see the census's own
|
|
1403
|
+
// comment for why that is the right superset to refuse.
|
|
1404
|
+
publishConstantFieldInitializers(input.classes, censusConstantFieldInitializers(input.bodies, input.classes, input.hosts.reactive.fields));
|
|
1398
1405
|
const structs = cppRecordDeclarations(input.plan, deriver, input.classes, input.hosts.reactive, boundRecordFields, structMembers, narrowedStorage.slots, input.wellKnownSymbols, perFile, input.reflection, emissionRepresentations, input.physicalClasses ?? input.classes, (body) => captures.of(body).kind === 'ok', fixedFieldStateConstant, singleEvaluationClasses);
|
|
1399
1406
|
const recursiveContainers = cppRecursiveContainerDeclarations(input.plan, emissionRepresentations);
|
|
1400
1407
|
// Real storage for every `ClassName.KEY = value` site the whole program
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geastack/compiler",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
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": {
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"check:contracts": "npm run build && node test/contracts.mjs"
|
|
68
68
|
},
|
|
69
69
|
"dependencies": {
|
|
70
|
-
"@geastack/node-compat": "^1.0.
|
|
70
|
+
"@geastack/node-compat": "^1.0.12",
|
|
71
71
|
"typescript": "^5.9.3"
|
|
72
72
|
},
|
|
73
73
|
"devDependencies": {
|
|
@@ -77,9 +77,9 @@
|
|
|
77
77
|
"prettier": "^3.8.3"
|
|
78
78
|
},
|
|
79
79
|
"peerDependencies": {
|
|
80
|
-
"@geastack/apple": ">=0.
|
|
81
|
-
"@geastack/geatsc-plugin-apple-native": ">=0.1.
|
|
82
|
-
"@geastack/geatsc-plugin-gea": ">=0.1.
|
|
80
|
+
"@geastack/apple": ">=0.2.8",
|
|
81
|
+
"@geastack/geatsc-plugin-apple-native": ">=0.1.3",
|
|
82
|
+
"@geastack/geatsc-plugin-gea": ">=0.1.6"
|
|
83
83
|
},
|
|
84
84
|
"peerDependenciesMeta": {
|
|
85
85
|
"@geastack/apple": {
|
|
@@ -89,8 +89,46 @@ inline Value ordinaryToPrimitive(const Value& value, ToPrimitiveHint hint) {
|
|
|
89
89
|
host::throwRuntimeError("TypeError", "Cannot convert object to primitive value");
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* ToPrimitive of a boxed typed array, which is always its ToString.
|
|
94
|
+
*
|
|
95
|
+
* A typed array is a native carrier with no dynamic property surface: it has no
|
|
96
|
+
* `@@toPrimitive`, its `valueOf` is Object's and returns the object, and
|
|
97
|
+
* nothing a program can do installs either on it. So 7.1.1 lands on
|
|
98
|
+
* `%TypedArray%.prototype.toString` for every hint, and that is knowable from
|
|
99
|
+
* the payload type alone -- without it the generic path below asks the box for
|
|
100
|
+
* `@@toPrimitive` and the box, having no field table, refuses by name. A view
|
|
101
|
+
* whose host brand states its own ToString (Node's Buffer) answers with that.
|
|
102
|
+
*/
|
|
103
|
+
template <typename Element>
|
|
104
|
+
inline bool boxedTypedArrayToStringAs(const Value& value, std::string& out) {
|
|
105
|
+
using Handle = gea::Ref<TypedArray<Element>>;
|
|
106
|
+
if (value.payloadType() != detail::payloadTypeTagFor<Handle>()) return false;
|
|
107
|
+
const Handle& view = value.as<Handle>();
|
|
108
|
+
if constexpr (std::is_same_v<Element, std::uint8_t>) {
|
|
109
|
+
if (view) {
|
|
110
|
+
if (const detail::HostViewToString render = detail::hostViewToStringFor(view->hostBrand())) {
|
|
111
|
+
out = render(*view);
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
out = runtime::array::join(view);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
inline bool boxedTypedArrayToString(const Value& value, std::string& out) {
|
|
121
|
+
if (value.tag() != Value::Tag::Object || value.isProxy()) return false;
|
|
122
|
+
return boxedTypedArrayToStringAs<std::uint8_t>(value, out) || boxedTypedArrayToStringAs<std::int8_t>(value, out) ||
|
|
123
|
+
boxedTypedArrayToStringAs<ClampedUint8>(value, out) || boxedTypedArrayToStringAs<std::int16_t>(value, out) ||
|
|
124
|
+
boxedTypedArrayToStringAs<std::uint16_t>(value, out) || boxedTypedArrayToStringAs<std::int32_t>(value, out) ||
|
|
125
|
+
boxedTypedArrayToStringAs<std::uint32_t>(value, out) || boxedTypedArrayToStringAs<float>(value, out) ||
|
|
126
|
+
boxedTypedArrayToStringAs<double>(value, out);
|
|
127
|
+
}
|
|
128
|
+
|
|
92
129
|
inline Value dynamicToPrimitive(const Value& value, ToPrimitiveHint hint = ToPrimitiveHint::Default) {
|
|
93
130
|
if (!isObjectValue(value)) return value;
|
|
131
|
+
if (std::string text; boxedTypedArrayToString(value, text)) return Value::box(Value::Tag::String, std::move(text));
|
|
94
132
|
const Value exotic = value.getProperty(PropertyKey::symbol(wellKnownSymbol(detail::WellKnownSymbol::ToPrimitive)));
|
|
95
133
|
if (exotic.tag() != Value::Tag::Undefined && exotic.tag() != Value::Tag::Null) {
|
|
96
134
|
if (exotic.tag() != Value::Tag::Function) host::throwRuntimeError("TypeError", "@@toPrimitive is not callable");
|
|
@@ -5649,6 +5649,7 @@ class TypedArray {
|
|
|
5649
5649
|
*/
|
|
5650
5650
|
void setHostBrand(const void* brand) { hostBrand_ = brand; }
|
|
5651
5651
|
bool hasHostBrand(const void* brand) const { return brand != nullptr && hostBrand_ == brand; }
|
|
5652
|
+
const void* hostBrand() const { return hostBrand_; }
|
|
5652
5653
|
|
|
5653
5654
|
/**
|
|
5654
5655
|
* ECMA-262 23.2.3.26 `%TypedArray%.prototype.set`, over a source view of any
|
|
@@ -5833,6 +5834,41 @@ class TypedArray {
|
|
|
5833
5834
|
const void* hostBrand_ = nullptr;
|
|
5834
5835
|
};
|
|
5835
5836
|
|
|
5837
|
+
/**
|
|
5838
|
+
* What a host brand's views answer to ToString.
|
|
5839
|
+
*
|
|
5840
|
+
* A brand is identity only (see `setHostBrand`), which is enough while the
|
|
5841
|
+
* view's type is known: `buffer.toString()` lowers to the host's own function.
|
|
5842
|
+
* Behind a box it is not. `body + chunk` in a `'data'` listener -- where Node
|
|
5843
|
+
* declares `chunk` as `any` and hands out a Buffer -- is ToPrimitive over an
|
|
5844
|
+
* erased byte view, and the generic answer for a byte view (23.2.3.32, the
|
|
5845
|
+
* comma-joined elements) is not Buffer's, which decodes the bytes as UTF-8.
|
|
5846
|
+
* The host that minted the brand is the only party that knows that, so it
|
|
5847
|
+
* states it here once, keyed by the same opaque address.
|
|
5848
|
+
*
|
|
5849
|
+
* `GEA_HOST_VIEW_TO_STRING` lets a host header register only when this runtime
|
|
5850
|
+
* has the table, so it still builds against a runtime that predates it.
|
|
5851
|
+
*/
|
|
5852
|
+
#define GEA_HOST_VIEW_TO_STRING 1
|
|
5853
|
+
namespace detail {
|
|
5854
|
+
using HostViewToString = std::string (*)(const TypedArray<std::uint8_t>&);
|
|
5855
|
+
inline std::vector<std::pair<const void*, HostViewToString>>& hostViewToStrings() {
|
|
5856
|
+
static std::vector<std::pair<const void*, HostViewToString>> table;
|
|
5857
|
+
return table;
|
|
5858
|
+
}
|
|
5859
|
+
inline bool registerHostViewToString(const void* brand, HostViewToString render) {
|
|
5860
|
+
hostViewToStrings().emplace_back(brand, render);
|
|
5861
|
+
return true;
|
|
5862
|
+
}
|
|
5863
|
+
inline HostViewToString hostViewToStringFor(const void* brand) {
|
|
5864
|
+
if (brand == nullptr) return nullptr;
|
|
5865
|
+
for (const auto& [known, render] : hostViewToStrings()) {
|
|
5866
|
+
if (known == brand) return render;
|
|
5867
|
+
}
|
|
5868
|
+
return nullptr;
|
|
5869
|
+
}
|
|
5870
|
+
} // namespace detail
|
|
5871
|
+
|
|
5836
5872
|
namespace runtime::atomics {
|
|
5837
5873
|
|
|
5838
5874
|
template <typename T>
|
|
@@ -6544,6 +6580,32 @@ struct MaxAlignOf<T, Rest...> {
|
|
|
6544
6580
|
static constexpr std::size_t value = alignof(T) > MaxAlignOf<Rest...>::value ? alignof(T) : MaxAlignOf<Rest...>::value;
|
|
6545
6581
|
};
|
|
6546
6582
|
|
|
6583
|
+
/**
|
|
6584
|
+
* Whether an arm may be the one a default-constructed union holds.
|
|
6585
|
+
*
|
|
6586
|
+
* A default-constructed `TaggedUnion` has to hold SOMETHING, and which arm it
|
|
6587
|
+
* picks is arbitrary -- the emitter only ever produces one to declare an SSA
|
|
6588
|
+
* slot that is assigned before it is read. Arm 0 is not always a legal choice:
|
|
6589
|
+
* `FunctionValue` deliberately has no default (a Function arm holding a
|
|
6590
|
+
* non-function is the bug its constructors exist to catch), so picking it
|
|
6591
|
+
* aborts a program that has done nothing wrong. Arms opt out here and the
|
|
6592
|
+
* default walks past them.
|
|
6593
|
+
*/
|
|
6594
|
+
template <typename Arm>
|
|
6595
|
+
struct UnionDefaultArm : std::true_type {};
|
|
6596
|
+
|
|
6597
|
+
/** The first arm that may be default-constructed; 0 when none opts in. */
|
|
6598
|
+
template <typename... Arms>
|
|
6599
|
+
struct DefaultUnionArmIndex {
|
|
6600
|
+
static constexpr std::size_t value = [] {
|
|
6601
|
+
constexpr bool allowed[] = {UnionDefaultArm<Arms>::value...};
|
|
6602
|
+
for (std::size_t index = 0; index < sizeof...(Arms); ++index) {
|
|
6603
|
+
if (allowed[index]) return index;
|
|
6604
|
+
}
|
|
6605
|
+
return static_cast<std::size_t>(0);
|
|
6606
|
+
}();
|
|
6607
|
+
};
|
|
6608
|
+
|
|
6547
6609
|
/** Lifetime dispatch over a closed arm set, by index: a raw byte buffer holds whichever arm is live, so construct/copy/move/destroy need a hand-written vtable-by-recursion in place of what the language generates for a real union of non-trivial arms. */
|
|
6548
6610
|
template <typename... Arms>
|
|
6549
6611
|
struct TaggedUnionOps;
|
|
@@ -6588,7 +6650,9 @@ class TaggedUnion {
|
|
|
6588
6650
|
}(std::index_sequence_for<Arms...>{});
|
|
6589
6651
|
}
|
|
6590
6652
|
|
|
6591
|
-
TaggedUnion() : index_(
|
|
6653
|
+
TaggedUnion() : index_(detail::DefaultUnionArmIndex<Arms...>::value) {
|
|
6654
|
+
detail::TaggedUnionOps<Arms...>::defaultConstruct(index_, &storage_);
|
|
6655
|
+
}
|
|
6592
6656
|
|
|
6593
6657
|
TaggedUnion(const TaggedUnion& other) : index_(other.index_) {
|
|
6594
6658
|
detail::TaggedUnionOps<Arms...>::copyConstruct(index_, &storage_, &other.storage_);
|
|
@@ -10021,6 +10085,19 @@ class FunctionValue : public Value {
|
|
|
10021
10085
|
}
|
|
10022
10086
|
};
|
|
10023
10087
|
|
|
10088
|
+
namespace detail {
|
|
10089
|
+
/**
|
|
10090
|
+
* Never the arm a default-constructed union holds.
|
|
10091
|
+
*
|
|
10092
|
+
* `FunctionValue()` aborts on purpose, so a union with a Function arm first
|
|
10093
|
+
* could not be declared at all: `gea_union_N v1;` -- the ordinary way the
|
|
10094
|
+
* emitter opens an SSA slot it assigns further down -- killed the process
|
|
10095
|
+
* before the assignment ran. `node:net`'s `Socket.pipe` is one such union.
|
|
10096
|
+
*/
|
|
10097
|
+
template <>
|
|
10098
|
+
struct UnionDefaultArm<FunctionValue> : std::false_type {};
|
|
10099
|
+
}
|
|
10100
|
+
|
|
10024
10101
|
namespace runtime {
|
|
10025
10102
|
namespace string {
|
|
10026
10103
|
inline std::size_t utf16Length(const std::string& value);
|
|
@@ -14682,6 +14759,50 @@ inline const Ref<DynamicObject>& Value::functionProperties() const {
|
|
|
14682
14759
|
|
|
14683
14760
|
Value dynamicFunctionPrototypeGet(const PropertyKey& key);
|
|
14684
14761
|
|
|
14762
|
+
namespace detail {
|
|
14763
|
+
/**
|
|
14764
|
+
* The own, non-method surface of a boxed typed array: `length`, `byteLength`,
|
|
14765
|
+
* `byteOffset` and the element at a canonical numeric index (ECMA-262 10.4.5,
|
|
14766
|
+
* 23.2.3).
|
|
14767
|
+
*
|
|
14768
|
+
* A typed array reaches a box wherever a library types it `any` -- Node's
|
|
14769
|
+
* `'data'` chunk is the measured case, and `total += chunk.length` is what a
|
|
14770
|
+
* byte-counting handler does with it. The payload type names the element type
|
|
14771
|
+
* exactly, so these four are answerable from the box with no table. Every other
|
|
14772
|
+
* key keeps the refusal below: a method (`slice`, `toString`) needs a callable
|
|
14773
|
+
* this runtime does not mint for an erased view, and answering `undefined`
|
|
14774
|
+
* would be the plausible wrong answer.
|
|
14775
|
+
*/
|
|
14776
|
+
template <typename Element>
|
|
14777
|
+
inline bool boxedTypedArrayOwnAs(const Value& value, const PropertyKey& key, Value& out) {
|
|
14778
|
+
using Handle = gea::Ref<TypedArray<Element>>;
|
|
14779
|
+
if (value.payloadType() != payloadTypeTagFor<Handle>()) return false;
|
|
14780
|
+
const Handle& view = value.as<Handle>();
|
|
14781
|
+
if (!view || key.isSymbol()) return false;
|
|
14782
|
+
std::size_t index = 0;
|
|
14783
|
+
if (arrayIndexOfKey(key, index)) {
|
|
14784
|
+
// 10.4.5.15: an index past the end reads `undefined`, never the prototype.
|
|
14785
|
+
out = index < view->size() ? Value::box(Value::Tag::Number, static_cast<double>((*view)[index])) : Value();
|
|
14786
|
+
return true;
|
|
14787
|
+
}
|
|
14788
|
+
if (key.isNumericSource()) return false;
|
|
14789
|
+
const std::string& name = key.text();
|
|
14790
|
+
if (name == "length") out = Value::box(Value::Tag::Number, static_cast<double>(view->size()));
|
|
14791
|
+
else if (name == "byteLength") out = Value::box(Value::Tag::Number, view->byteLength());
|
|
14792
|
+
else if (name == "byteOffset") out = Value::box(Value::Tag::Number, view->byteOffset());
|
|
14793
|
+
else return false;
|
|
14794
|
+
return true;
|
|
14795
|
+
}
|
|
14796
|
+
|
|
14797
|
+
inline bool boxedTypedArrayOwn(const Value& value, const PropertyKey& key, Value& out) {
|
|
14798
|
+
return boxedTypedArrayOwnAs<std::uint8_t>(value, key, out) || boxedTypedArrayOwnAs<std::int8_t>(value, key, out) ||
|
|
14799
|
+
boxedTypedArrayOwnAs<ClampedUint8>(value, key, out) || boxedTypedArrayOwnAs<std::int16_t>(value, key, out) ||
|
|
14800
|
+
boxedTypedArrayOwnAs<std::uint16_t>(value, key, out) || boxedTypedArrayOwnAs<std::int32_t>(value, key, out) ||
|
|
14801
|
+
boxedTypedArrayOwnAs<std::uint32_t>(value, key, out) || boxedTypedArrayOwnAs<float>(value, key, out) ||
|
|
14802
|
+
boxedTypedArrayOwnAs<double>(value, key, out);
|
|
14803
|
+
}
|
|
14804
|
+
} // namespace detail
|
|
14805
|
+
|
|
14685
14806
|
inline Value Value::getProperty(const PropertyKey& key) const {
|
|
14686
14807
|
return getProperty(key, *this);
|
|
14687
14808
|
}
|
|
@@ -14739,6 +14860,8 @@ inline Value Value::getProperty(const PropertyKey& key, const Value& receiver) c
|
|
|
14739
14860
|
if (tag_ == Tag::Object) {
|
|
14740
14861
|
Value method;
|
|
14741
14862
|
if (detail::boxedPromiseMethod(*this, key, method)) return method;
|
|
14863
|
+
Value own;
|
|
14864
|
+
if (detail::boxedTypedArrayOwn(*this, key, own)) return own;
|
|
14742
14865
|
}
|
|
14743
14866
|
if (tag_ == Tag::Object || tag_ == Tag::Function) detail::refuseOpaquePropertyAccess("a property read", key);
|
|
14744
14867
|
// Every remaining box is a primitive, whose properties all live on a
|