@geastack/compiler 1.0.15 → 1.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/conversion/nodes.d.ts +31 -0
- package/dist/conversion/nodes.js +46 -2
- package/dist/conversion/record-view.d.ts +24 -0
- package/dist/conversion/record-view.js +54 -0
- package/dist/ir/certify.js +23 -1
- package/dist/ir/integers.js +24 -2
- package/dist/ir/lower-operands.d.ts +10 -1
- package/dist/ir/lower-operands.js +58 -2
- package/dist/ir/lower.js +12 -3
- package/dist/ir/reflection-demand.js +5 -0
- package/dist/ir/transfer.js +17 -8
- package/dist/plugins/apple/host.d.ts +2 -0
- package/dist/plugins/apple/host.js +13 -6
- package/dist/plugins/gea/host.d.ts +2 -0
- package/dist/plugins/gea/host.js +11 -4
- package/dist/plugins/host-package.d.ts +37 -0
- package/dist/plugins/host-package.js +28 -0
- package/dist/plugins/load.js +15 -0
- package/dist/plugins/webgl/host.d.ts +9 -3
- package/dist/plugins/webgl/host.js +15 -6
- package/dist/plugins/webgl/plugin.js +4 -1
- package/dist/representation/embedded-carriers.d.ts +22 -0
- package/dist/representation/embedded-carriers.js +105 -0
- package/dist/semantics/frontend.js +4 -1
- package/dist/semantics/model/operands.d.ts +17 -0
- package/dist/semantics/model/operations.d.ts +28 -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/producers/allocations.js +2 -0
- package/dist/semantics/normalize/producers/bindings.js +5 -1
- package/dist/semantics/normalize/producers/boundary.d.ts +1 -0
- package/dist/semantics/normalize/producers/boundary.js +6 -2
- package/dist/semantics/normalize/producers/class-lifecycle.js +3 -0
- package/dist/semantics/normalize/producers/control.js +2 -2
- package/dist/semantics/normalize/producers/erasure.d.ts +7 -0
- package/dist/semantics/normalize/producers/erasure.js +19 -0
- package/dist/semantics/normalize/producers/exact-arms.d.ts +12 -0
- package/dist/semantics/normalize/producers/exact-arms.js +12 -0
- package/dist/semantics/normalize/producers/invocations.js +33 -1
- 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/class-properties/emit-class-properties.js +4 -4
- package/dist/targets/cpp/conversions.js +63 -1
- package/dist/targets/cpp/emit-callable.js +7 -6
- package/dist/targets/cpp/emit-context.d.ts +38 -1
- package/dist/targets/cpp/emit-context.js +28 -2
- package/dist/targets/cpp/emit-narrowing.d.ts +1 -1
- package/dist/targets/cpp/emit-narrowing.js +54 -1
- package/dist/targets/cpp/emit-properties.js +13 -3
- package/dist/targets/cpp/emit-record-view.js +31 -0
- package/dist/targets/cpp/emit.d.ts +3 -3
- package/dist/targets/cpp/emit.js +11 -5
- 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 +32 -16
- package/package.json +5 -5
- package/src/targets/cpp/runtime/gea_dynamic_proxy.h +38 -0
- package/src/targets/cpp/runtime/gea_runtime.h +247 -45
|
@@ -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';
|
|
@@ -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,
|
|
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
|
|
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
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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
|
/**
|
|
@@ -1395,6 +1389,13 @@ export const renderTranslationUnit = (input) => {
|
|
|
1395
1389
|
// actual reader in this same function -- `cppRecordDeclarations` itself --
|
|
1396
1390
|
// so it cannot be published after, the way that one is.
|
|
1397
1391
|
publishLazyArrowFieldPlans(input.classes, censusLazyArrowFields(input.bodies, input.classes, captures));
|
|
1392
|
+
// Published alongside it, and read by the same constructor emission: which
|
|
1393
|
+
// field initializers are a single constant, so a store that writes what
|
|
1394
|
+
// value-initialization already wrote can be dropped. Passed the plugin's own
|
|
1395
|
+
// `reactive.fields` rather than the settled `celledFields`, which
|
|
1396
|
+
// `cppRecordDeclarations` has not produced yet -- see the census's own
|
|
1397
|
+
// comment for why that is the right superset to refuse.
|
|
1398
|
+
publishConstantFieldInitializers(input.classes, censusConstantFieldInitializers(input.bodies, input.classes, input.hosts.reactive.fields));
|
|
1398
1399
|
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
1400
|
const recursiveContainers = cppRecursiveContainerDeclarations(input.plan, emissionRepresentations);
|
|
1400
1401
|
// Real storage for every `ClassName.KEY = value` site the whole program
|
|
@@ -1632,9 +1633,24 @@ export const renderTranslationUnit = (input) => {
|
|
|
1632
1633
|
}
|
|
1633
1634
|
return false;
|
|
1634
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
|
+
}
|
|
1635
1651
|
const thunks = new Map();
|
|
1636
1652
|
for (const body of input.bodies) {
|
|
1637
|
-
const thunk = thunkOf(body, captures, formalsNarrowedIn(body.sourceOwner), resultNarrowedIn(body.sourceOwner), linkage
|
|
1653
|
+
const thunk = thunkOf(body, captures, formalsNarrowedIn(body.sourceOwner), resultNarrowedIn(body.sourceOwner), linkage);
|
|
1638
1654
|
if (thunk)
|
|
1639
1655
|
thunks.set(body, thunk);
|
|
1640
1656
|
}
|
|
@@ -1706,7 +1722,7 @@ export const renderTranslationUnit = (input) => {
|
|
|
1706
1722
|
continue;
|
|
1707
1723
|
}
|
|
1708
1724
|
try {
|
|
1709
|
-
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);
|
|
1710
1726
|
const stableEntry = stableBorrowEntries.get(cppBodyName(body.sourceOwner));
|
|
1711
1727
|
const commonJsScope = commonJsOwner === null || commonJsOwner.nativeRecord
|
|
1712
1728
|
? []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geastack/compiler",
|
|
3
|
-
"version": "1.0.
|
|
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": {
|
|
@@ -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");
|
|
@@ -3170,6 +3170,25 @@ struct CallableObject<Result(Arguments...)> {
|
|
|
3170
3170
|
return true;
|
|
3171
3171
|
}
|
|
3172
3172
|
|
|
3173
|
+
// The same registration, performed where a function OBJECT is minted from
|
|
3174
|
+
// the thunk rather than by a namespace-scope initializer beside the thunk.
|
|
3175
|
+
// A namespace-scope registration is a static initializer that takes the
|
|
3176
|
+
// thunk's address, and a static initializer with a side effect is a GC
|
|
3177
|
+
// root: neither `--gc-sections` nor LTO may drop it, so it kept every
|
|
3178
|
+
// emitted function -- and its source text -- in the binary whether or not
|
|
3179
|
+
// anything reached it. One `EventEmitter` boxing a listener turned the
|
|
3180
|
+
// registry on for a whole program, and that pinned all 384 functions of a
|
|
3181
|
+
// raw HTTP server. Here the registration is reachable only from a site
|
|
3182
|
+
// that creates the function object, which is the only site that can later
|
|
3183
|
+
// observe its `name`/`length`/source. The function-local static is one
|
|
3184
|
+
// per `Entry`, so several minting sites of one declaration share a single
|
|
3185
|
+
// registration, and a site costs one guard-variable load per mint.
|
|
3186
|
+
template <Invoke Entry>
|
|
3187
|
+
static Invoke entryWithFacts(std::string_view name, std::size_t length, std::string_view text) {
|
|
3188
|
+
static const SourceRegistration registration{Entry, name, length, text, nullptr};
|
|
3189
|
+
return Entry;
|
|
3190
|
+
}
|
|
3191
|
+
|
|
3173
3192
|
// An adapter (`dropArguments` and its siblings below) shares ONE thunk
|
|
3174
3193
|
// pointer across every closure it wraps, so the facts it reports cannot
|
|
3175
3194
|
// live in this registration -- they belong to whichever `Source` closure
|
|
@@ -5649,6 +5668,7 @@ class TypedArray {
|
|
|
5649
5668
|
*/
|
|
5650
5669
|
void setHostBrand(const void* brand) { hostBrand_ = brand; }
|
|
5651
5670
|
bool hasHostBrand(const void* brand) const { return brand != nullptr && hostBrand_ == brand; }
|
|
5671
|
+
const void* hostBrand() const { return hostBrand_; }
|
|
5652
5672
|
|
|
5653
5673
|
/**
|
|
5654
5674
|
* ECMA-262 23.2.3.26 `%TypedArray%.prototype.set`, over a source view of any
|
|
@@ -5833,6 +5853,41 @@ class TypedArray {
|
|
|
5833
5853
|
const void* hostBrand_ = nullptr;
|
|
5834
5854
|
};
|
|
5835
5855
|
|
|
5856
|
+
/**
|
|
5857
|
+
* What a host brand's views answer to ToString.
|
|
5858
|
+
*
|
|
5859
|
+
* A brand is identity only (see `setHostBrand`), which is enough while the
|
|
5860
|
+
* view's type is known: `buffer.toString()` lowers to the host's own function.
|
|
5861
|
+
* Behind a box it is not. `body + chunk` in a `'data'` listener -- where Node
|
|
5862
|
+
* declares `chunk` as `any` and hands out a Buffer -- is ToPrimitive over an
|
|
5863
|
+
* erased byte view, and the generic answer for a byte view (23.2.3.32, the
|
|
5864
|
+
* comma-joined elements) is not Buffer's, which decodes the bytes as UTF-8.
|
|
5865
|
+
* The host that minted the brand is the only party that knows that, so it
|
|
5866
|
+
* states it here once, keyed by the same opaque address.
|
|
5867
|
+
*
|
|
5868
|
+
* `GEA_HOST_VIEW_TO_STRING` lets a host header register only when this runtime
|
|
5869
|
+
* has the table, so it still builds against a runtime that predates it.
|
|
5870
|
+
*/
|
|
5871
|
+
#define GEA_HOST_VIEW_TO_STRING 1
|
|
5872
|
+
namespace detail {
|
|
5873
|
+
using HostViewToString = std::string (*)(const TypedArray<std::uint8_t>&);
|
|
5874
|
+
inline std::vector<std::pair<const void*, HostViewToString>>& hostViewToStrings() {
|
|
5875
|
+
static std::vector<std::pair<const void*, HostViewToString>> table;
|
|
5876
|
+
return table;
|
|
5877
|
+
}
|
|
5878
|
+
inline bool registerHostViewToString(const void* brand, HostViewToString render) {
|
|
5879
|
+
hostViewToStrings().emplace_back(brand, render);
|
|
5880
|
+
return true;
|
|
5881
|
+
}
|
|
5882
|
+
inline HostViewToString hostViewToStringFor(const void* brand) {
|
|
5883
|
+
if (brand == nullptr) return nullptr;
|
|
5884
|
+
for (const auto& [known, render] : hostViewToStrings()) {
|
|
5885
|
+
if (known == brand) return render;
|
|
5886
|
+
}
|
|
5887
|
+
return nullptr;
|
|
5888
|
+
}
|
|
5889
|
+
} // namespace detail
|
|
5890
|
+
|
|
5836
5891
|
namespace runtime::atomics {
|
|
5837
5892
|
|
|
5838
5893
|
template <typename T>
|
|
@@ -6544,6 +6599,32 @@ struct MaxAlignOf<T, Rest...> {
|
|
|
6544
6599
|
static constexpr std::size_t value = alignof(T) > MaxAlignOf<Rest...>::value ? alignof(T) : MaxAlignOf<Rest...>::value;
|
|
6545
6600
|
};
|
|
6546
6601
|
|
|
6602
|
+
/**
|
|
6603
|
+
* Whether an arm may be the one a default-constructed union holds.
|
|
6604
|
+
*
|
|
6605
|
+
* A default-constructed `TaggedUnion` has to hold SOMETHING, and which arm it
|
|
6606
|
+
* picks is arbitrary -- the emitter only ever produces one to declare an SSA
|
|
6607
|
+
* slot that is assigned before it is read. Arm 0 is not always a legal choice:
|
|
6608
|
+
* `FunctionValue` deliberately has no default (a Function arm holding a
|
|
6609
|
+
* non-function is the bug its constructors exist to catch), so picking it
|
|
6610
|
+
* aborts a program that has done nothing wrong. Arms opt out here and the
|
|
6611
|
+
* default walks past them.
|
|
6612
|
+
*/
|
|
6613
|
+
template <typename Arm>
|
|
6614
|
+
struct UnionDefaultArm : std::true_type {};
|
|
6615
|
+
|
|
6616
|
+
/** The first arm that may be default-constructed; 0 when none opts in. */
|
|
6617
|
+
template <typename... Arms>
|
|
6618
|
+
struct DefaultUnionArmIndex {
|
|
6619
|
+
static constexpr std::size_t value = [] {
|
|
6620
|
+
constexpr bool allowed[] = {UnionDefaultArm<Arms>::value...};
|
|
6621
|
+
for (std::size_t index = 0; index < sizeof...(Arms); ++index) {
|
|
6622
|
+
if (allowed[index]) return index;
|
|
6623
|
+
}
|
|
6624
|
+
return static_cast<std::size_t>(0);
|
|
6625
|
+
}();
|
|
6626
|
+
};
|
|
6627
|
+
|
|
6547
6628
|
/** 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
6629
|
template <typename... Arms>
|
|
6549
6630
|
struct TaggedUnionOps;
|
|
@@ -6588,7 +6669,9 @@ class TaggedUnion {
|
|
|
6588
6669
|
}(std::index_sequence_for<Arms...>{});
|
|
6589
6670
|
}
|
|
6590
6671
|
|
|
6591
|
-
TaggedUnion() : index_(
|
|
6672
|
+
TaggedUnion() : index_(detail::DefaultUnionArmIndex<Arms...>::value) {
|
|
6673
|
+
detail::TaggedUnionOps<Arms...>::defaultConstruct(index_, &storage_);
|
|
6674
|
+
}
|
|
6592
6675
|
|
|
6593
6676
|
TaggedUnion(const TaggedUnion& other) : index_(other.index_) {
|
|
6594
6677
|
detail::TaggedUnionOps<Arms...>::copyConstruct(index_, &storage_, &other.storage_);
|
|
@@ -10021,6 +10104,19 @@ class FunctionValue : public Value {
|
|
|
10021
10104
|
}
|
|
10022
10105
|
};
|
|
10023
10106
|
|
|
10107
|
+
namespace detail {
|
|
10108
|
+
/**
|
|
10109
|
+
* Never the arm a default-constructed union holds.
|
|
10110
|
+
*
|
|
10111
|
+
* `FunctionValue()` aborts on purpose, so a union with a Function arm first
|
|
10112
|
+
* could not be declared at all: `gea_union_N v1;` -- the ordinary way the
|
|
10113
|
+
* emitter opens an SSA slot it assigns further down -- killed the process
|
|
10114
|
+
* before the assignment ran. `node:net`'s `Socket.pipe` is one such union.
|
|
10115
|
+
*/
|
|
10116
|
+
template <>
|
|
10117
|
+
struct UnionDefaultArm<FunctionValue> : std::false_type {};
|
|
10118
|
+
}
|
|
10119
|
+
|
|
10024
10120
|
namespace runtime {
|
|
10025
10121
|
namespace string {
|
|
10026
10122
|
inline std::size_t utf16Length(const std::string& value);
|
|
@@ -14682,6 +14778,50 @@ inline const Ref<DynamicObject>& Value::functionProperties() const {
|
|
|
14682
14778
|
|
|
14683
14779
|
Value dynamicFunctionPrototypeGet(const PropertyKey& key);
|
|
14684
14780
|
|
|
14781
|
+
namespace detail {
|
|
14782
|
+
/**
|
|
14783
|
+
* The own, non-method surface of a boxed typed array: `length`, `byteLength`,
|
|
14784
|
+
* `byteOffset` and the element at a canonical numeric index (ECMA-262 10.4.5,
|
|
14785
|
+
* 23.2.3).
|
|
14786
|
+
*
|
|
14787
|
+
* A typed array reaches a box wherever a library types it `any` -- Node's
|
|
14788
|
+
* `'data'` chunk is the measured case, and `total += chunk.length` is what a
|
|
14789
|
+
* byte-counting handler does with it. The payload type names the element type
|
|
14790
|
+
* exactly, so these four are answerable from the box with no table. Every other
|
|
14791
|
+
* key keeps the refusal below: a method (`slice`, `toString`) needs a callable
|
|
14792
|
+
* this runtime does not mint for an erased view, and answering `undefined`
|
|
14793
|
+
* would be the plausible wrong answer.
|
|
14794
|
+
*/
|
|
14795
|
+
template <typename Element>
|
|
14796
|
+
inline bool boxedTypedArrayOwnAs(const Value& value, const PropertyKey& key, Value& out) {
|
|
14797
|
+
using Handle = gea::Ref<TypedArray<Element>>;
|
|
14798
|
+
if (value.payloadType() != payloadTypeTagFor<Handle>()) return false;
|
|
14799
|
+
const Handle& view = value.as<Handle>();
|
|
14800
|
+
if (!view || key.isSymbol()) return false;
|
|
14801
|
+
std::size_t index = 0;
|
|
14802
|
+
if (arrayIndexOfKey(key, index)) {
|
|
14803
|
+
// 10.4.5.15: an index past the end reads `undefined`, never the prototype.
|
|
14804
|
+
out = index < view->size() ? Value::box(Value::Tag::Number, static_cast<double>((*view)[index])) : Value();
|
|
14805
|
+
return true;
|
|
14806
|
+
}
|
|
14807
|
+
if (key.isNumericSource()) return false;
|
|
14808
|
+
const std::string& name = key.text();
|
|
14809
|
+
if (name == "length") out = Value::box(Value::Tag::Number, static_cast<double>(view->size()));
|
|
14810
|
+
else if (name == "byteLength") out = Value::box(Value::Tag::Number, view->byteLength());
|
|
14811
|
+
else if (name == "byteOffset") out = Value::box(Value::Tag::Number, view->byteOffset());
|
|
14812
|
+
else return false;
|
|
14813
|
+
return true;
|
|
14814
|
+
}
|
|
14815
|
+
|
|
14816
|
+
inline bool boxedTypedArrayOwn(const Value& value, const PropertyKey& key, Value& out) {
|
|
14817
|
+
return boxedTypedArrayOwnAs<std::uint8_t>(value, key, out) || boxedTypedArrayOwnAs<std::int8_t>(value, key, out) ||
|
|
14818
|
+
boxedTypedArrayOwnAs<ClampedUint8>(value, key, out) || boxedTypedArrayOwnAs<std::int16_t>(value, key, out) ||
|
|
14819
|
+
boxedTypedArrayOwnAs<std::uint16_t>(value, key, out) || boxedTypedArrayOwnAs<std::int32_t>(value, key, out) ||
|
|
14820
|
+
boxedTypedArrayOwnAs<std::uint32_t>(value, key, out) || boxedTypedArrayOwnAs<float>(value, key, out) ||
|
|
14821
|
+
boxedTypedArrayOwnAs<double>(value, key, out);
|
|
14822
|
+
}
|
|
14823
|
+
} // namespace detail
|
|
14824
|
+
|
|
14685
14825
|
inline Value Value::getProperty(const PropertyKey& key) const {
|
|
14686
14826
|
return getProperty(key, *this);
|
|
14687
14827
|
}
|
|
@@ -14739,6 +14879,8 @@ inline Value Value::getProperty(const PropertyKey& key, const Value& receiver) c
|
|
|
14739
14879
|
if (tag_ == Tag::Object) {
|
|
14740
14880
|
Value method;
|
|
14741
14881
|
if (detail::boxedPromiseMethod(*this, key, method)) return method;
|
|
14882
|
+
Value own;
|
|
14883
|
+
if (detail::boxedTypedArrayOwn(*this, key, own)) return own;
|
|
14742
14884
|
}
|
|
14743
14885
|
if (tag_ == Tag::Object || tag_ == Tag::Function) detail::refuseOpaquePropertyAccess("a property read", key);
|
|
14744
14886
|
// Every remaining box is a primitive, whose properties all live on a
|
|
@@ -18207,6 +18349,31 @@ template <typename T>
|
|
|
18207
18349
|
throw gea::Value::box(gea::Value::Tag::String, std::string("InternalError: entered a branch the compiler proved unreachable"));
|
|
18208
18350
|
}
|
|
18209
18351
|
|
|
18352
|
+
/**
|
|
18353
|
+
* The exact-arm projection of a tagged union (`conversion/nodes.ts`'s
|
|
18354
|
+
* `exactArmFor`): the payload of arm `Index`, or a `TypeError` when the value
|
|
18355
|
+
* holds another arm.
|
|
18356
|
+
*
|
|
18357
|
+
* Emitted inside a body whose declaration states `@gea-exact-arms`, and at a
|
|
18358
|
+
* value the program's own `as T` states the arm of where the census has no
|
|
18359
|
+
* sound per-arm answer (`lower-operands.ts`'s `assertedArmEntry`). The
|
|
18360
|
+
* alternative the compiler would otherwise render is a per-arm dispatch that
|
|
18361
|
+
* CONVERTS every arm into the target -- for a callable target, an adapter that
|
|
18362
|
+
* boxes the typed arm's parameters to feed a generic listener -- and the tag
|
|
18363
|
+
* is the author stating that the guard around the cast already excluded those
|
|
18364
|
+
* arms. A `TypeError` rather than an abort because a wrong arm here is the
|
|
18365
|
+
* program's contract violated (`server.on('request', genericHandler)`), not
|
|
18366
|
+
* the compiler's, and the program may want to catch it.
|
|
18367
|
+
*/
|
|
18368
|
+
template <std::size_t Index, typename... Arms>
|
|
18369
|
+
typename gea::TaggedUnion<Arms...>::template ArmType<Index> exactArm(const gea::TaggedUnion<Arms...>& value) {
|
|
18370
|
+
if (!value.template is<Index>()) {
|
|
18371
|
+
throwRuntimeError("TypeError", "Value is not the union arm this operation requires (arm " + std::to_string(Index) +
|
|
18372
|
+
" expected, arm " + std::to_string(value.index()) + " held)");
|
|
18373
|
+
}
|
|
18374
|
+
return value.template get<Index>();
|
|
18375
|
+
}
|
|
18376
|
+
|
|
18210
18377
|
} // namespace gea::host
|
|
18211
18378
|
|
|
18212
18379
|
namespace gea::runtime::string {
|
|
@@ -24927,6 +25094,77 @@ inline void recordNodeSubscription(int nodeId, std::function<void()> release);
|
|
|
24927
25094
|
template <typename Node>
|
|
24928
25095
|
gea::Ref<bool> nodeLiveToken(const Node& node);
|
|
24929
25096
|
|
|
25097
|
+
/**
|
|
25098
|
+
* Whether the engine dispatches `name` off-tree. `rotary` (the encoder dial)
|
|
25099
|
+
* always: it has no element target, the runtime hands each detent to the one
|
|
25100
|
+
* document-level listener chain (`dispatchDocumentRotary`), and
|
|
25101
|
+
* `Tree::setEventListener` drops "rotary" as a type it never walks the tree
|
|
25102
|
+
* for -- so an `onRotary` delegated to the body was registered nowhere and the
|
|
25103
|
+
* dial did nothing, silently. `keydown` is off-tree on anything but an
|
|
25104
|
+
* `<input>`: the runtime hands a key to the focused input's own listener (which
|
|
25105
|
+
* bubbles to the body) and then to the document-level chain. The same rule the
|
|
25106
|
+
* gea plugin's `cpp-mounted-lowering.ts` applies to its direct-root listeners.
|
|
25107
|
+
*/
|
|
25108
|
+
template <typename Node>
|
|
25109
|
+
bool isDocumentLevelEvent(const Node& node, const std::string& name) {
|
|
25110
|
+
if (name == "rotary") return true;
|
|
25111
|
+
if (name != "keydown") return false;
|
|
25112
|
+
const char* tag = node.tagName();
|
|
25113
|
+
if (!tag) return true;
|
|
25114
|
+
std::string lowered(tag);
|
|
25115
|
+
for (char& character : lowered) character = static_cast<char>(std::tolower(static_cast<unsigned char>(character)));
|
|
25116
|
+
return lowered != "input";
|
|
25117
|
+
}
|
|
25118
|
+
|
|
25119
|
+
/**
|
|
25120
|
+
* Register `invoke` for `name` on behalf of `node`: the one place both engine
|
|
25121
|
+
* `addNodeListener` overloads bind through.
|
|
25122
|
+
*
|
|
25123
|
+
* A document-level event goes straight to `Document::addEventListener`, which
|
|
25124
|
+
* chains it onto the off-tree singleton: no `containsNode` gate, since there is
|
|
25125
|
+
* no target to contain. The chain has no per-listener removal, so the handler
|
|
25126
|
+
* is gated on the node's live token instead -- a rebuilt subtree leaves an
|
|
25127
|
+
* inert closure behind rather than a second generation of the handler.
|
|
25128
|
+
*
|
|
25129
|
+
* Everything else is delegated to the BODY and gated by `containsNode`; see
|
|
25130
|
+
* the no-argument `addNodeListener` for why, and `recordNodeSubscription` for
|
|
25131
|
+
* how it is released with the node.
|
|
25132
|
+
*/
|
|
25133
|
+
template <typename Node, typename Invoke>
|
|
25134
|
+
void bindNodeListener(Node& node, const std::string& name, Invoke invoke) {
|
|
25135
|
+
const auto target = node;
|
|
25136
|
+
auto run = [target, invoke](gea::framework::events::PointerEvent& event) mutable {
|
|
25137
|
+
const auto previousTarget = event.currentTarget;
|
|
25138
|
+
const auto previousTargetId = event.currentTargetId;
|
|
25139
|
+
const auto previousPhase = event.eventPhase;
|
|
25140
|
+
event.currentTarget = gea::framework::events::EventTarget(target.id());
|
|
25141
|
+
event.currentTargetId = target.id();
|
|
25142
|
+
event.eventPhase = event.targetId == target.id() ? gea::framework::events::EventPhase::AtTarget
|
|
25143
|
+
: gea::framework::events::EventPhase::Bubbling;
|
|
25144
|
+
invoke(event);
|
|
25145
|
+
event.currentTarget = previousTarget;
|
|
25146
|
+
event.currentTargetId = previousTargetId;
|
|
25147
|
+
event.eventPhase = previousPhase;
|
|
25148
|
+
};
|
|
25149
|
+
if (isDocumentLevelEvent(node, name)) {
|
|
25150
|
+
auto alive = nodeLiveToken(node);
|
|
25151
|
+
gea::embedded::ui::Document::instance().addEventListener(
|
|
25152
|
+
name.c_str(), [alive, run](gea::framework::events::PointerEvent& event) mutable {
|
|
25153
|
+
if (!*alive) return;
|
|
25154
|
+
run(event);
|
|
25155
|
+
});
|
|
25156
|
+
return;
|
|
25157
|
+
}
|
|
25158
|
+
const auto listenerId = gea::embedded::ui::Document::instance().body().addEventListener(
|
|
25159
|
+
name.c_str(), [target, run](gea::framework::events::PointerEvent& event) mutable {
|
|
25160
|
+
if (!gea::embedded::ui::Tree::instance().containsNode(target.id(), event.targetId)) return;
|
|
25161
|
+
run(event);
|
|
25162
|
+
});
|
|
25163
|
+
recordNodeSubscription(target.id(), [name, listenerId]() {
|
|
25164
|
+
gea::embedded::ui::Document::instance().body().removeEventListener(name.c_str(), listenerId);
|
|
25165
|
+
});
|
|
25166
|
+
}
|
|
25167
|
+
|
|
24930
25168
|
/**
|
|
24931
25169
|
* `onClick={() => ...}` on the real engine: the handler is copied into the
|
|
24932
25170
|
* listener the engine holds, and the event it hands back is dropped -- a
|
|
@@ -24969,26 +25207,7 @@ void addNodeListener(Node& node, const std::string& type, const gea::CallableObj
|
|
|
24969
25207
|
// flipped the unit twice and the whole UI stayed in Celsius, with nothing
|
|
24970
25208
|
// logged and every node still rendering correctly.
|
|
24971
25209
|
const gea::CallableObject<Result()> held = handler;
|
|
24972
|
-
|
|
24973
|
-
const std::string name = eventTypeOf(type);
|
|
24974
|
-
const auto listenerId = gea::embedded::ui::Document::instance().body().addEventListener(
|
|
24975
|
-
name.c_str(), [held, target](gea::framework::events::PointerEvent& event) mutable {
|
|
24976
|
-
if (!gea::embedded::ui::Tree::instance().containsNode(target.id(), event.targetId)) return;
|
|
24977
|
-
const auto previousTarget = event.currentTarget;
|
|
24978
|
-
const auto previousTargetId = event.currentTargetId;
|
|
24979
|
-
const auto previousPhase = event.eventPhase;
|
|
24980
|
-
event.currentTarget = gea::framework::events::EventTarget(target.id());
|
|
24981
|
-
event.currentTargetId = target.id();
|
|
24982
|
-
event.eventPhase = event.targetId == target.id() ? gea::framework::events::EventPhase::AtTarget
|
|
24983
|
-
: gea::framework::events::EventPhase::Bubbling;
|
|
24984
|
-
held.call();
|
|
24985
|
-
event.currentTarget = previousTarget;
|
|
24986
|
-
event.currentTargetId = previousTargetId;
|
|
24987
|
-
event.eventPhase = previousPhase;
|
|
24988
|
-
});
|
|
24989
|
-
recordNodeSubscription(target.id(), [name, listenerId]() {
|
|
24990
|
-
gea::embedded::ui::Document::instance().body().removeEventListener(name.c_str(), listenerId);
|
|
24991
|
-
});
|
|
25210
|
+
bindNodeListener(node, eventTypeOf(type), [held](gea::framework::events::PointerEvent&) mutable { held.call(); });
|
|
24992
25211
|
}
|
|
24993
25212
|
|
|
24994
25213
|
/**
|
|
@@ -25018,31 +25237,14 @@ void addNodeListener(Node& node, const std::string& type, const gea::CallableObj
|
|
|
25018
25237
|
template <typename Node, typename Result, typename Argument>
|
|
25019
25238
|
void addNodeListener(Node& node, const std::string& type, const gea::CallableObject<Result(Argument)>& handler) {
|
|
25020
25239
|
const gea::CallableObject<Result(Argument)> held = handler;
|
|
25021
|
-
const auto target = node;
|
|
25022
25240
|
const std::string name = eventTypeOf(type);
|
|
25023
|
-
|
|
25024
|
-
|
|
25025
|
-
|
|
25026
|
-
|
|
25027
|
-
|
|
25028
|
-
|
|
25029
|
-
|
|
25030
|
-
event.currentTargetId = target.id();
|
|
25031
|
-
event.eventPhase = event.targetId == target.id() ? gea::framework::events::EventPhase::AtTarget
|
|
25032
|
-
: gea::framework::events::EventPhase::Bubbling;
|
|
25033
|
-
if constexpr (std::is_constructible_v<std::decay_t<Argument>, const gea::framework::events::PointerEvent&>) {
|
|
25034
|
-
held.call(static_cast<std::decay_t<Argument>>(event));
|
|
25035
|
-
} else {
|
|
25036
|
-
std::fprintf(stderr, "gea: the handler for \"%s\" declares an event parameter, which no host table states a carrier for\n", name.c_str());
|
|
25037
|
-
gea::detail::abortAfterFlush();
|
|
25038
|
-
}
|
|
25039
|
-
event.currentTarget = previousTarget;
|
|
25040
|
-
event.currentTargetId = previousTargetId;
|
|
25041
|
-
event.eventPhase = previousPhase;
|
|
25042
|
-
});
|
|
25043
|
-
// Owned by `node`, for the reason the no-argument overload above states.
|
|
25044
|
-
recordNodeSubscription(target.id(), [name, listenerId]() {
|
|
25045
|
-
gea::embedded::ui::Document::instance().body().removeEventListener(name.c_str(), listenerId);
|
|
25241
|
+
bindNodeListener(node, name, [held, name](gea::framework::events::PointerEvent& event) mutable {
|
|
25242
|
+
if constexpr (std::is_constructible_v<std::decay_t<Argument>, const gea::framework::events::PointerEvent&>) {
|
|
25243
|
+
held.call(static_cast<std::decay_t<Argument>>(event));
|
|
25244
|
+
} else {
|
|
25245
|
+
std::fprintf(stderr, "gea: the handler for \"%s\" declares an event parameter, which no host table states a carrier for\n", name.c_str());
|
|
25246
|
+
gea::detail::abortAfterFlush();
|
|
25247
|
+
}
|
|
25046
25248
|
});
|
|
25047
25249
|
}
|
|
25048
25250
|
|