gofront 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +50 -19
- package/src/backend/js/index.js +100 -55
- package/src/backend/js/runtime.js +102 -0
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +96 -16
- package/src/backend/js/stdlib/errors.js +2 -2
- package/src/backend/js/stdlib/fmt.js +26 -8
- package/src/backend/js/stdlib/index.js +3 -0
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +1 -1
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +34 -9
- package/src/backend/js/stdlib/strings.js +4 -1
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +3 -3
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +486 -33
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +606 -5009
- package/src/backend/wasm/encode.js +212 -64
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +1426 -49
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +159 -35
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +275 -72
- package/src/compiler.js +144 -21
- package/src/index.js +86 -115
- package/src/lower/boxing.js +3 -4
- package/src/lower/captures.js +10 -17
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +5 -10
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/resolver.js +2 -1
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +142 -2
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +17 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +206 -7
- package/src/vendor.js +2 -2
|
@@ -20,12 +20,22 @@
|
|
|
20
20
|
// TypedArray inputs accepted). No copy-back for slices.
|
|
21
21
|
// - func values: JS callbacks wrapped into wasm closures via thunks; wasm
|
|
22
22
|
// closures exposed to JS as callable functions.
|
|
23
|
-
// -
|
|
23
|
+
// - named non-empty interfaces (Task H5.2): wasm values reach JS as their
|
|
24
|
+
// handle class / live view when the dynamic type is an exported `*T`,
|
|
25
|
+
// otherwise as a facade whose methods call the wasm dispatchers. Only
|
|
26
|
+
// wasm-owned values (`__ref`) go back in; JS-implemented values are
|
|
27
|
+
// rejected (no cross-boundary itab proxies).
|
|
28
|
+
// - maps, anonymous interfaces, error, pointers to non-structs: rejected
|
|
24
29
|
// with a compile error (planned for a later boundary version).
|
|
30
|
+
// - `gofront/shared` buffers (Task H6.1): `(base, len)` views over the
|
|
31
|
+
// module's exported linear memory; JS sees a zero-copy TypedArray over
|
|
32
|
+
// `memory.buffer`. Only TypedArrays that already alias that memory (ones
|
|
33
|
+
// handed out by this facade) may be passed back in.
|
|
25
34
|
// - `*testing.T`: the harness's JS test object, passed as an opaque
|
|
26
35
|
// externref (test methods are routed back to JS via `env.testing_*`).
|
|
27
36
|
|
|
28
|
-
import {
|
|
37
|
+
import { HELPER_SPRINTF, HELPER_STRCONV } from "../js/runtime.js";
|
|
38
|
+
import { getSharedInfo, isTestingT } from "./types.js";
|
|
29
39
|
|
|
30
40
|
// ── Type classification ──────────────────────────────────────
|
|
31
41
|
|
|
@@ -79,7 +89,7 @@ function funcSigFromAst(t) {
|
|
|
79
89
|
|
|
80
90
|
// Classifies a Go type (AST node or checker type) for boundary marshalling.
|
|
81
91
|
// Returns a descriptor `{ k, ... }`; `k === "unsupported"` carries `what`.
|
|
82
|
-
|
|
92
|
+
function classifyType(goType, mod) {
|
|
83
93
|
if (!goType) return { k: "void" };
|
|
84
94
|
if (isTestingT(goType)) return { k: "extern", wType: "externref" };
|
|
85
95
|
|
|
@@ -95,6 +105,8 @@ export function classifyType(goType, mod) {
|
|
|
95
105
|
switch (t.kind) {
|
|
96
106
|
case "named":
|
|
97
107
|
return classifyNamed(t, mod);
|
|
108
|
+
case "shared":
|
|
109
|
+
return classifyShared(t, mod);
|
|
98
110
|
case "untyped":
|
|
99
111
|
return classifyBasic(t.base);
|
|
100
112
|
case "basic":
|
|
@@ -108,7 +120,7 @@ export function classifyType(goType, mod) {
|
|
|
108
120
|
case "func":
|
|
109
121
|
return classifyFunc(t, mod);
|
|
110
122
|
case "interface":
|
|
111
|
-
return classifyInterface(t);
|
|
123
|
+
return classifyInterface(t, mod);
|
|
112
124
|
default:
|
|
113
125
|
return {
|
|
114
126
|
k: "unsupported",
|
|
@@ -183,6 +195,17 @@ function classifyPointer(t, mod) {
|
|
|
183
195
|
return { k: "unsupported", what: `pointer to ${what}` };
|
|
184
196
|
}
|
|
185
197
|
|
|
198
|
+
function classifyShared(t, mod) {
|
|
199
|
+
const info = getSharedInfo(t, mod.checker);
|
|
200
|
+
return {
|
|
201
|
+
k: "shared",
|
|
202
|
+
key: info.key,
|
|
203
|
+
ctor: info.ctor,
|
|
204
|
+
bytes: info.bytes,
|
|
205
|
+
wType: mod.getSharedType().wType,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
186
209
|
function classifySlice(t, mod) {
|
|
187
210
|
const elem = classifyType(t.elem, mod);
|
|
188
211
|
if (elem.k === "unsupported") return elem;
|
|
@@ -216,31 +239,66 @@ function classifyArray(t, mod) {
|
|
|
216
239
|
};
|
|
217
240
|
}
|
|
218
241
|
|
|
219
|
-
function
|
|
242
|
+
function classifySig(t, mod) {
|
|
220
243
|
const params = (t.params ?? []).map((p) => classifyType(p, mod));
|
|
221
244
|
const returns = (t.returns ?? [])
|
|
222
245
|
.map((r) => classifyType(r, mod))
|
|
223
246
|
.filter((d) => d.k !== "void");
|
|
224
247
|
const bad = [...params, ...returns].find((d) => d.k === "unsupported");
|
|
225
|
-
|
|
248
|
+
return bad ?? { params, returns };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function classifyFunc(t, mod) {
|
|
252
|
+
const sig = classifySig(t, mod);
|
|
253
|
+
if (sig.k === "unsupported") return sig;
|
|
226
254
|
const closureInfo = mod.getClosureType(t);
|
|
227
255
|
return {
|
|
228
256
|
k: "func",
|
|
229
|
-
params,
|
|
230
|
-
returns,
|
|
257
|
+
params: sig.params,
|
|
258
|
+
returns: sig.returns,
|
|
231
259
|
closureInfo,
|
|
232
260
|
key: `c${closureInfo.typeIndex}`,
|
|
233
261
|
wType: { kind: "ref", nullable: true, typeIndex: closureInfo.typeIndex },
|
|
234
262
|
};
|
|
235
263
|
}
|
|
236
264
|
|
|
237
|
-
function classifyInterface(t) {
|
|
265
|
+
function classifyInterface(t, mod) {
|
|
238
266
|
if (t.name === "error") return { k: "unsupported", what: "error" };
|
|
239
267
|
if (!t.methods || t.methods.size === 0) return { k: "any", wType: "anyref" };
|
|
240
|
-
|
|
241
|
-
k: "unsupported",
|
|
242
|
-
|
|
268
|
+
if (!t.name || !(t.methods instanceof Map))
|
|
269
|
+
return { k: "unsupported", what: "anonymous interface" };
|
|
270
|
+
mod._boundaryIfaces ??= new Map();
|
|
271
|
+
return mod._boundaryIfaces.get(t.name) ?? classifyNamedInterface(t, mod);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function classifyNamedInterface(t, mod) {
|
|
275
|
+
const desc = {
|
|
276
|
+
k: "iface",
|
|
277
|
+
name: t.name,
|
|
278
|
+
key: t.name,
|
|
279
|
+
wType: "anyref",
|
|
280
|
+
methods: [],
|
|
281
|
+
concrete: [],
|
|
243
282
|
};
|
|
283
|
+
// Cached before the walk so self-referencing method signatures terminate.
|
|
284
|
+
mod._boundaryIfaces.set(t.name, desc);
|
|
285
|
+
for (const [mName, sigType] of t.methods) {
|
|
286
|
+
const sig = classifySig(sigType, mod);
|
|
287
|
+
if (sig.k === "unsupported") {
|
|
288
|
+
const err = {
|
|
289
|
+
k: "unsupported",
|
|
290
|
+
what: `${sig.what} (in method ${t.name}.${mName})`,
|
|
291
|
+
};
|
|
292
|
+
mod._boundaryIfaces.set(t.name, err);
|
|
293
|
+
return err;
|
|
294
|
+
}
|
|
295
|
+
desc.methods.push({
|
|
296
|
+
name: mName,
|
|
297
|
+
dispatch: `__dispatch_${t.name}_${mName}`,
|
|
298
|
+
...sig,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
return desc;
|
|
244
302
|
}
|
|
245
303
|
|
|
246
304
|
// ── Export metadata ──────────────────────────────────────────
|
|
@@ -291,6 +349,7 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
|
|
|
291
349
|
structs: [],
|
|
292
350
|
funcs: [],
|
|
293
351
|
consts: [],
|
|
352
|
+
vars: [], // exported package-level shared buffers of wasm packages
|
|
294
353
|
structByName: new Map(),
|
|
295
354
|
_mod: mod,
|
|
296
355
|
};
|
|
@@ -304,6 +363,8 @@ export function collectBoundaryMeta(progs, funcDecls, mod) {
|
|
|
304
363
|
collectStructDecl(d, pkgName, pkgTarget, meta, mod);
|
|
305
364
|
} else if (d.kind === "ConstDecl" && pkgTarget === "wasm") {
|
|
306
365
|
collectConstDecl(d, pkgName, meta, errors);
|
|
366
|
+
} else if (d.kind === "VarDecl" && pkgTarget === "wasm") {
|
|
367
|
+
collectSharedVarDecl(d, meta, mod);
|
|
307
368
|
}
|
|
308
369
|
}
|
|
309
370
|
}
|
|
@@ -350,6 +411,22 @@ function checkDesc(desc, where, errors) {
|
|
|
350
411
|
return desc;
|
|
351
412
|
}
|
|
352
413
|
|
|
414
|
+
// Exported `var Buf = shared.NewFloat32(n)` globals are exposed to JS as
|
|
415
|
+
// TypedArray views (read once after instantiation; sizing is fixed).
|
|
416
|
+
function collectSharedVarDecl(d, meta, mod) {
|
|
417
|
+
for (const spec of d.decls ?? d.specs ?? [d]) {
|
|
418
|
+
const names = spec.names ?? (spec.name ? [spec.name] : []);
|
|
419
|
+
const values = spec.value ?? (spec.init ? [spec.init] : []);
|
|
420
|
+
for (let i = 0; i < names.length; i++) {
|
|
421
|
+
const name = names[i];
|
|
422
|
+
if (!isExported(name)) continue;
|
|
423
|
+
const goType = spec.type ?? values[i]?._type;
|
|
424
|
+
if (!getSharedInfo(goType, mod.checker)) continue;
|
|
425
|
+
meta.vars.push({ name, desc: classifyType(goType, mod) });
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
353
430
|
function collectMethodMeta(fn, meta, errors, mod) {
|
|
354
431
|
if (!isExported(fn._methodName)) return;
|
|
355
432
|
const s = meta.structByName.get(fn._recvTypeName);
|
|
@@ -358,6 +435,7 @@ function collectMethodMeta(fn, meta, errors, mod) {
|
|
|
358
435
|
s.methods.push({
|
|
359
436
|
name: fn._methodName,
|
|
360
437
|
exportName: fn._exportName,
|
|
438
|
+
fnName: fn.name,
|
|
361
439
|
ptrRecv: fn.params[0].type?.kind === "pointer",
|
|
362
440
|
params: fn.params
|
|
363
441
|
.slice(1)
|
|
@@ -418,11 +496,37 @@ function visitNeed(desc, needs, errors, mod) {
|
|
|
418
496
|
needs.funcs.set(desc.key, desc);
|
|
419
497
|
visitSignatureNeeds(desc, needs, errors, mod);
|
|
420
498
|
return;
|
|
499
|
+
case "iface":
|
|
500
|
+
if (needs.ifaces.has(desc.key)) return;
|
|
501
|
+
needs.ifaces.set(desc.key, desc);
|
|
502
|
+
for (const m of desc.methods) visitSignatureNeeds(m, needs, errors, mod);
|
|
503
|
+
return;
|
|
504
|
+
case "shared":
|
|
505
|
+
needs.shared = true;
|
|
506
|
+
return;
|
|
421
507
|
default:
|
|
422
508
|
return;
|
|
423
509
|
}
|
|
424
510
|
}
|
|
425
511
|
|
|
512
|
+
// Exported `*T` types (already on the boundary surface) whose JS class exposes
|
|
513
|
+
// every interface method: such values reach JS as the handle / live view
|
|
514
|
+
// itself, so type switches and identity still work. `__itag` matches by
|
|
515
|
+
// `ref.test`, so a struct is only chosen for values that really hold it.
|
|
516
|
+
function concreteCandidates(desc, needs, meta, mod) {
|
|
517
|
+
const out = [];
|
|
518
|
+
for (const [name, { info }] of needs.structs) {
|
|
519
|
+
const both = info.pkgTarget === "both";
|
|
520
|
+
const names = both
|
|
521
|
+
? [...(mod.checker?.types?.get(name)?.underlying?.methods?.keys() ?? [])]
|
|
522
|
+
: (meta.structByName.get(name)?.methods ?? []).map((m) => m.name);
|
|
523
|
+
const have = new Set(names);
|
|
524
|
+
if (!desc.methods.every((m) => have.has(m.name))) continue;
|
|
525
|
+
out.push({ name, typeIndex: info.typeIndex, both });
|
|
526
|
+
}
|
|
527
|
+
return out;
|
|
528
|
+
}
|
|
529
|
+
|
|
426
530
|
function visitSignatureNeeds(sig, needs, errors, mod) {
|
|
427
531
|
for (const p of sig.params) visitNeed(p, needs, errors, mod);
|
|
428
532
|
for (const r of sig.returns) visitNeed(r, needs, errors, mod);
|
|
@@ -437,6 +541,8 @@ function collectNeeds(meta) {
|
|
|
437
541
|
slices: new Map(), // key -> desc
|
|
438
542
|
arrays: new Map(), // key -> desc
|
|
439
543
|
funcs: new Map(), // key -> desc
|
|
544
|
+
ifaces: new Map(), // interface name -> desc
|
|
545
|
+
shared: false, // any shared buffer on the surface
|
|
440
546
|
};
|
|
441
547
|
const errors = [];
|
|
442
548
|
for (const s of meta.structs) {
|
|
@@ -445,7 +551,10 @@ function collectNeeds(meta) {
|
|
|
445
551
|
for (const m of s.methods) visitSignatureNeeds(m, needs, errors, mod);
|
|
446
552
|
}
|
|
447
553
|
for (const f of meta.funcs) visitSignatureNeeds(f, needs, errors, mod);
|
|
554
|
+
for (const v of meta.vars ?? []) visitNeed(v.desc, needs, errors, mod);
|
|
448
555
|
if (errors.length > 0) throw new Error([...new Set(errors)].join("\n"));
|
|
556
|
+
for (const desc of needs.ifaces.values())
|
|
557
|
+
desc.concrete = concreteCandidates(desc, needs, meta, mod);
|
|
449
558
|
return needs;
|
|
450
559
|
}
|
|
451
560
|
|
|
@@ -800,7 +909,8 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
800
909
|
// get a trampoline (`__x_<export>`) with the boundary signature; the facade
|
|
801
910
|
// calls that instead of the raw export.
|
|
802
911
|
const addTrampoline = (entry) => {
|
|
803
|
-
|
|
912
|
+
// Methods are keyed `T.M` in funcMap but exported as `T_M`.
|
|
913
|
+
const fnIdx = mod.funcMap.get(entry.fnName ?? entry.exportName);
|
|
804
914
|
if (fnIdx === undefined) return;
|
|
805
915
|
const sig = typeAt(mod, mod.funcs[fnIdx - importFuncCount]?.typeIndex);
|
|
806
916
|
if (sig?.form !== "func") return;
|
|
@@ -823,6 +933,101 @@ export function addBoundaryHelpers(mod, meta) {
|
|
|
823
933
|
};
|
|
824
934
|
for (const s of meta.structs) for (const m of s.methods) addTrampoline(m);
|
|
825
935
|
for (const f of meta.funcs) addTrampoline(f);
|
|
936
|
+
for (const desc of needs.ifaces.values())
|
|
937
|
+
addIfaceHelpers(desc, mod, addFunc, addTrampoline);
|
|
938
|
+
|
|
939
|
+
if (needs.shared) {
|
|
940
|
+
const { wType, typeIndex } = mod.getSharedType();
|
|
941
|
+
addFunc(
|
|
942
|
+
"__shared_base",
|
|
943
|
+
[EXT],
|
|
944
|
+
["i32"],
|
|
945
|
+
[...getIn(0, wType), { op: "struct.get", typeIndex, fieldIndex: 0 }],
|
|
946
|
+
);
|
|
947
|
+
addFunc(
|
|
948
|
+
"__shared_len",
|
|
949
|
+
[EXT],
|
|
950
|
+
["i32"],
|
|
951
|
+
[...getIn(0, wType), { op: "struct.get", typeIndex, fieldIndex: 1 }],
|
|
952
|
+
);
|
|
953
|
+
// JS-side views (e.g. a `subarray`) go back in as a fresh (base, len).
|
|
954
|
+
addFunc(
|
|
955
|
+
"__shared_make",
|
|
956
|
+
["i32", "i32"],
|
|
957
|
+
[EXT],
|
|
958
|
+
[get(0), get(1), { op: "struct.new", typeIndex }, ...toExt(wType)],
|
|
959
|
+
);
|
|
960
|
+
for (const v of meta.vars ?? []) {
|
|
961
|
+
const g = mod.resolveGlobal(v.name);
|
|
962
|
+
if (!g) continue;
|
|
963
|
+
addFunc(
|
|
964
|
+
`__var_${v.name}`,
|
|
965
|
+
[],
|
|
966
|
+
[EXT],
|
|
967
|
+
[{ op: "global.get", index: g.index }, ...toExt(wType)],
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// `(externref) -> i32`: `ref.test`s param 0 against each type index and
|
|
974
|
+
// returns `resultFor(i)` for the first match, 0 otherwise.
|
|
975
|
+
function refTestChain(typeIndices, resultFor) {
|
|
976
|
+
const body = typeIndices.flatMap((ti, i) => [
|
|
977
|
+
{ op: "local.get", index: 0 },
|
|
978
|
+
{ op: "any.convert_extern" },
|
|
979
|
+
{ op: "ref.test", typeIndex: ti },
|
|
980
|
+
{ op: "if", blockType: "void" },
|
|
981
|
+
{ op: "i32.const", value: resultFor(i) },
|
|
982
|
+
{ op: "return" },
|
|
983
|
+
{ op: "end" },
|
|
984
|
+
]);
|
|
985
|
+
body.push({ op: "i32.const", value: 0 });
|
|
986
|
+
return body;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Type indices (incl. unexported and boxed-value types) whose method set
|
|
990
|
+
// covers the interface, from the same candidate search the dispatchers use.
|
|
991
|
+
function implementingTypeIndices(desc, mod) {
|
|
992
|
+
let acc = null;
|
|
993
|
+
for (const m of desc.methods) {
|
|
994
|
+
const have = new Set(
|
|
995
|
+
mod.findInterfaceCandidates(null, m.name).map((c) => c.testTypeIndex),
|
|
996
|
+
);
|
|
997
|
+
acc = acc === null ? have : new Set([...acc].filter((t) => have.has(t)));
|
|
998
|
+
}
|
|
999
|
+
return [...(acc ?? [])];
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Interfaces: method calls go through the existing `ref.test` dispatchers;
|
|
1003
|
+
// `__impl$<I>` tells whether a wasm value implements I (guards JS inputs);
|
|
1004
|
+
// `__itag$<I>` reports which exported `*T` (1-based) a value holds, 0 if none.
|
|
1005
|
+
function addIfaceHelpers(desc, mod, addFunc, addTrampoline) {
|
|
1006
|
+
for (const m of desc.methods) {
|
|
1007
|
+
const entry = { exportName: m.dispatch };
|
|
1008
|
+
addTrampoline(entry);
|
|
1009
|
+
if (!entry.callName)
|
|
1010
|
+
throw new Error(
|
|
1011
|
+
`internal: no wasm dispatcher for interface method ${desc.name}.${m.name}`,
|
|
1012
|
+
);
|
|
1013
|
+
m.callName = entry.callName;
|
|
1014
|
+
}
|
|
1015
|
+
addFunc(
|
|
1016
|
+
`__impl$${desc.key}`,
|
|
1017
|
+
["externref"],
|
|
1018
|
+
["i32"],
|
|
1019
|
+
refTestChain(implementingTypeIndices(desc, mod), () => 1),
|
|
1020
|
+
);
|
|
1021
|
+
if (desc.concrete.length === 0) return;
|
|
1022
|
+
addFunc(
|
|
1023
|
+
`__itag$${desc.key}`,
|
|
1024
|
+
["externref"],
|
|
1025
|
+
["i32"],
|
|
1026
|
+
refTestChain(
|
|
1027
|
+
desc.concrete.map((c) => c.typeIndex),
|
|
1028
|
+
(i) => i + 1,
|
|
1029
|
+
),
|
|
1030
|
+
);
|
|
826
1031
|
}
|
|
827
1032
|
|
|
828
1033
|
// ── JS facade generation ─────────────────────────────────────
|
|
@@ -848,13 +1053,19 @@ function inExpr(desc, v) {
|
|
|
848
1053
|
case "struct":
|
|
849
1054
|
if (desc.info.pkgTarget === "both")
|
|
850
1055
|
return `${P}to_${desc.name}(${v}, ${desc.ptr ? "false" : "true"})`;
|
|
851
|
-
return desc.ptr
|
|
1056
|
+
return desc.ptr
|
|
1057
|
+
? `${P}href_${desc.name}(${v})`
|
|
1058
|
+
: `${P}hval_${desc.name}(${v})`;
|
|
852
1059
|
case "slice":
|
|
853
1060
|
return `${P}slin_${desc.key}(${v})`;
|
|
854
1061
|
case "array":
|
|
855
1062
|
return `${P}arrin_${desc.key}(${v})`;
|
|
856
1063
|
case "func":
|
|
857
1064
|
return `${P}fnin_${desc.key}(${v})`;
|
|
1065
|
+
case "iface":
|
|
1066
|
+
return `${P}ifin_${desc.key}(${v})`;
|
|
1067
|
+
case "shared":
|
|
1068
|
+
return `${P}shin(${v}, ${desc.ctor})`;
|
|
858
1069
|
default:
|
|
859
1070
|
return v;
|
|
860
1071
|
}
|
|
@@ -881,6 +1092,10 @@ function outExpr(desc, v) {
|
|
|
881
1092
|
return `${P}arrout_${desc.key}(${v})`;
|
|
882
1093
|
case "func":
|
|
883
1094
|
return `${P}fnout_${desc.key}(${v})`;
|
|
1095
|
+
case "iface":
|
|
1096
|
+
return `${P}ifout_${desc.key}(${v})`;
|
|
1097
|
+
case "shared":
|
|
1098
|
+
return `${P}shout(${v}, ${desc.ctor})`;
|
|
884
1099
|
default:
|
|
885
1100
|
return v;
|
|
886
1101
|
}
|
|
@@ -989,15 +1204,37 @@ function indent(text, prefix) {
|
|
|
989
1204
|
// quoted, helpers prefixed with `__`.
|
|
990
1205
|
export const WASM_IMPORTS_JS = `const ${P}MAX = 9007199254740991n;
|
|
991
1206
|
function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
992
|
-
let
|
|
1207
|
+
let lineBuf = "";
|
|
993
1208
|
let targs = [];
|
|
994
|
-
const
|
|
995
|
-
const
|
|
996
|
-
const
|
|
1209
|
+
const late = { exports: null };
|
|
1210
|
+
const flush = () => { write(lineBuf); lineBuf = ""; };
|
|
1211
|
+
const print = (v) => {
|
|
1212
|
+
const s = String(v);
|
|
1213
|
+
if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
|
|
1214
|
+
lineBuf += " ";
|
|
1215
|
+
}
|
|
1216
|
+
lineBuf += s;
|
|
1217
|
+
};
|
|
1218
|
+
const println = (v) => {
|
|
1219
|
+
const s = String(v);
|
|
1220
|
+
if (lineBuf.length > 0 && !lineBuf.endsWith(" ") && !s.startsWith(" ")) {
|
|
1221
|
+
lineBuf += " ";
|
|
1222
|
+
}
|
|
1223
|
+
lineBuf += s;
|
|
1224
|
+
flush();
|
|
1225
|
+
};
|
|
997
1226
|
const targ = (v) => { targs.push(v); };
|
|
998
1227
|
const env = {
|
|
999
1228
|
"panicTag": tag,
|
|
1000
|
-
"panic": (msg) => {
|
|
1229
|
+
"panic": (msg) => {
|
|
1230
|
+
let str;
|
|
1231
|
+
try {
|
|
1232
|
+
str = String(msg);
|
|
1233
|
+
} catch {
|
|
1234
|
+
str = "[panic object]";
|
|
1235
|
+
}
|
|
1236
|
+
throw new Error(str);
|
|
1237
|
+
},
|
|
1001
1238
|
"str": (i) => stringTable[i] ?? "",
|
|
1002
1239
|
"str_len": (s) => (s ? s.length : 0),
|
|
1003
1240
|
"str_concat": (a, b) => (a ?? "") + (b ?? ""),
|
|
@@ -1011,6 +1248,33 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
|
1011
1248
|
"str_slice": (s, a, b) => (s ? s.slice(a, b) : ""),
|
|
1012
1249
|
"str_from_code_point": (c) => String.fromCodePoint(c),
|
|
1013
1250
|
"str_code_point_at": (s, i) => (s ? s.codePointAt(i) : 0),
|
|
1251
|
+
"str_hash": (s) => {
|
|
1252
|
+
if (!s) return 0;
|
|
1253
|
+
let h = 0;
|
|
1254
|
+
for (let i = 0; i < s.length; i++) {
|
|
1255
|
+
h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
|
|
1256
|
+
}
|
|
1257
|
+
return h;
|
|
1258
|
+
},
|
|
1259
|
+
"str_to_upper": (s) => (s ? s.toUpperCase() : ""),
|
|
1260
|
+
"str_to_lower": (s) => (s ? s.toLowerCase() : ""),
|
|
1261
|
+
"str_trim_space": (s) => (s ? s.trim() : ""),
|
|
1262
|
+
"str_contains": (s, sub) => (s && sub !== undefined ? (s.includes(sub) ? 1 : 0) : 0),
|
|
1263
|
+
"str_has_prefix": (s, pre) => (s && pre !== undefined ? (s.startsWith(pre) ? 1 : 0) : 0),
|
|
1264
|
+
"str_has_suffix": (s, suf) => (s && suf !== undefined ? (s.endsWith(suf) ? 1 : 0) : 0),
|
|
1265
|
+
"str_index": (s, sub) => (s ? s.indexOf(sub) : -1),
|
|
1266
|
+
"str_last_index": (s, sub) => (s ? s.lastIndexOf(sub) : -1),
|
|
1267
|
+
"str_repeat": (s, n) => (s && n > 0 ? s.repeat(Number(n)) : ""),
|
|
1268
|
+
"str_replace_all": (s, o, n) => (s ? s.replaceAll(o, n) : ""),
|
|
1269
|
+
"str_equal_fold": (a, b) => ((a ?? "").toLowerCase() === (b ?? "").toLowerCase() ? 1 : 0),
|
|
1270
|
+
"str_count": (s, sep) => {
|
|
1271
|
+
if (!s) return sep === "" ? 1 : 0;
|
|
1272
|
+
if (sep === "") return s.length + 1;
|
|
1273
|
+
return s.split(sep).length - 1;
|
|
1274
|
+
},
|
|
1275
|
+
"str_from_i64": (n) => String(n),
|
|
1276
|
+
"str_from_i32": (n) => String(n),
|
|
1277
|
+
"str_from_f64": (f) => String(f),
|
|
1014
1278
|
"is_string": (v) => (typeof v === "string" ? 1 : 0),
|
|
1015
1279
|
"print_i32": print, "print_i64": print, "print_f32": print, "print_f64": print,
|
|
1016
1280
|
"print_str": print, "print_any": print,
|
|
@@ -1027,14 +1291,107 @@ function ${P}imports(stringTable, extraEnv, tag, write) {
|
|
|
1027
1291
|
"testing_name": (t) => t.Name(),
|
|
1028
1292
|
"testing_flag": (t, i) => (t[stringTable[i]]() ? 1 : 0),
|
|
1029
1293
|
};
|
|
1294
|
+
// Stdlib imports (strings/strconv/utf8/fmt/t.Run) are a separate block so
|
|
1295
|
+
// modules that never call them do not ship it (see WASM_STDLIB_JS).
|
|
1296
|
+
if (typeof ${P}stdlib === "function") {
|
|
1297
|
+
Object.assign(env, ${P}stdlib(stringTable, {
|
|
1298
|
+
"take": () => { const a = targs; targs = []; return a; },
|
|
1299
|
+
"printf": (s) => {
|
|
1300
|
+
lineBuf += s;
|
|
1301
|
+
const nl = lineBuf.lastIndexOf("\\n");
|
|
1302
|
+
if (nl >= 0) { write(lineBuf.slice(0, nl)); lineBuf = lineBuf.slice(nl + 1); }
|
|
1303
|
+
},
|
|
1304
|
+
"late": late,
|
|
1305
|
+
}));
|
|
1306
|
+
}
|
|
1030
1307
|
Object.assign(env, extraEnv);
|
|
1031
1308
|
const m = {};
|
|
1032
|
-
for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "
|
|
1033
|
-
|
|
1309
|
+
for (const k of ["sin", "cos", "tan", "asin", "acos", "atan", "atan2", "pow", "exp", "log", "log2", "log10", "cbrt", "hypot"]) m[k] = Math[k];
|
|
1310
|
+
m.mod = (x, y) => x % y;
|
|
1311
|
+
// Go rounds half away from zero; Math.round rounds half toward +Infinity.
|
|
1312
|
+
m.round = (x) => Math.sign(x) * Math.round(Math.abs(x));
|
|
1313
|
+
// Exports are needed by imports that call back into the module (t.Run).
|
|
1314
|
+
return { "env": env, "Math": m, "__bind": (ex) => { late.exports = ex; } };
|
|
1315
|
+
}`;
|
|
1316
|
+
|
|
1317
|
+
// JS side of the STDLIB_IMPORTS table (index.js); emitted only when the
|
|
1318
|
+
// module imports one of the packages that use it.
|
|
1319
|
+
export const WASM_STDLIB_JS = `${HELPER_SPRINTF}
|
|
1320
|
+
${HELPER_STRCONV}
|
|
1321
|
+
function ${P}stdlib(stringTable, io) {
|
|
1322
|
+
let parts = [];
|
|
1323
|
+
let strconvErr = null;
|
|
1324
|
+
let decodeSize = 0;
|
|
1325
|
+
return {
|
|
1326
|
+
"testing_run": (t, name, fn) => (t.Run(name, (sub) => { io.late.exports.__testing_run_cb(fn, sub); }) ? 1 : 0),
|
|
1327
|
+
"fmt_sprintf": (f) => __sprintf(f ?? "", ...io.take()),
|
|
1328
|
+
"fmt_printf": (f) => { io.printf(__sprintf(f ?? "", ...io.take())); },
|
|
1329
|
+
"str_trim": (s, c) => ${P}trim(s ?? "", c ?? "", true, true),
|
|
1330
|
+
"str_trim_left": (s, c) => ${P}trim(s ?? "", c ?? "", true, false),
|
|
1331
|
+
"str_trim_right": (s, c) => ${P}trim(s ?? "", c ?? "", false, true),
|
|
1332
|
+
"str_trim_prefix": (s, p) => (s && p && s.startsWith(p) ? s.slice(p.length) : (s ?? "")),
|
|
1333
|
+
"str_trim_suffix": (s, p) => (s && p && s.endsWith(p) ? s.slice(0, s.length - p.length) : (s ?? "")),
|
|
1334
|
+
"str_replace": (s, o, n, cnt) => {
|
|
1335
|
+
s = s ?? ""; o = o ?? ""; n = n ?? "";
|
|
1336
|
+
if (cnt < 0) return s.replaceAll(o, n);
|
|
1337
|
+
let out = "", from = 0;
|
|
1338
|
+
for (let k = 0; k < cnt; k++) {
|
|
1339
|
+
const at = s.indexOf(o, from);
|
|
1340
|
+
if (at < 0) break;
|
|
1341
|
+
out += s.slice(from, at) + n;
|
|
1342
|
+
from = at + o.length;
|
|
1343
|
+
if (o === "") { if (from >= s.length) break; out += s[from]; from++; }
|
|
1344
|
+
}
|
|
1345
|
+
return out + s.slice(from);
|
|
1346
|
+
},
|
|
1347
|
+
"str_contains_rune": (s, r) => ((s ?? "").includes(String.fromCodePoint(r)) ? 1 : 0),
|
|
1348
|
+
"str_contains_any": (s, cs) => { for (const c of (cs ?? "")) if ((s ?? "").includes(c)) return 1; return 0; },
|
|
1349
|
+
"str_index_byte": (s, b) => (s ?? "").indexOf(String.fromCharCode(b)),
|
|
1350
|
+
"str_index_rune": (s, r) => (s ?? "").indexOf(String.fromCodePoint(r)),
|
|
1351
|
+
"str_index_any": (s, cs) => { s = s ?? ""; let best = -1; for (const c of (cs ?? "")) { const i = s.indexOf(c); if (i >= 0 && (best < 0 || i < best)) best = i; } return best; },
|
|
1352
|
+
"str_last_index_byte": (s, b) => (s ?? "").lastIndexOf(String.fromCharCode(b)),
|
|
1353
|
+
"str_title": (s) => (s ?? "").replace(/(^|[^\\p{L}\\p{N}_'])(\\p{Ll})/gu, (m, a, b) => a + b.toUpperCase()),
|
|
1354
|
+
"str_split": (s, sep) => { parts = (s ?? "").split(sep ?? ""); return parts.length; },
|
|
1355
|
+
"str_fields": (s) => { parts = (s ?? "").split(/\\s+/).filter((p) => p !== ""); return parts.length; },
|
|
1356
|
+
"str_part": (i) => parts[i] ?? "",
|
|
1357
|
+
"strconv_atoi": (s) => { const r = __strconv_atoi(s ?? ""); strconvErr = r[1]; return r[0]; },
|
|
1358
|
+
"strconv_parse_int": (s, base, bits) => { const r = __strconv_parse_int(s ?? "", base, bits); strconvErr = r[1]; return r[0]; },
|
|
1359
|
+
"strconv_parse_float": (s) => { const r = __strconv_parse_float(s ?? ""); strconvErr = r[1]; return r[0]; },
|
|
1360
|
+
"strconv_parse_bool": (s) => { const r = __strconv_parse_bool(s ?? ""); strconvErr = r[1]; return r[0] ? 1 : 0; },
|
|
1361
|
+
"strconv_err": () => strconvErr,
|
|
1362
|
+
"strconv_format_float": (f, fmtc, prec, _bits) => __strconv_format_float(f, fmtc, prec),
|
|
1363
|
+
"strconv_format_int": (n, base) => n.toString(base),
|
|
1364
|
+
"strconv_quote": (s) => JSON.stringify(s ?? ""),
|
|
1365
|
+
"utf8_rune_count": (s) => { let n = 0; for (const _ of (s ?? "")) n++; return n; },
|
|
1366
|
+
"utf8_rune_len": (r) => (r < 0 || r > 0x10ffff || (r >= 0xd800 && r <= 0xdfff) ? -1 : r < 0x80 ? 1 : r < 0x800 ? 2 : r < 0x10000 ? 3 : 4),
|
|
1367
|
+
"utf8_valid_string": (s) => (s ?? "").isWellFormed ? ((s ?? "").isWellFormed() ? 1 : 0) : (/[\\uD800-\\uDFFF]/.test((s ?? "").replace(/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g, "")) ? 0 : 1),
|
|
1368
|
+
"utf8_decode_rune": (s, last) => {
|
|
1369
|
+
s = s ?? "";
|
|
1370
|
+
if (s.length === 0) { decodeSize = 0; return 0xfffd; }
|
|
1371
|
+
let cp;
|
|
1372
|
+
if (last) { const tail = s.codePointAt(s.length - 1); cp = (tail >= 0xdc00 && tail <= 0xdfff && s.length >= 2) ? s.codePointAt(s.length - 2) : tail; }
|
|
1373
|
+
else cp = s.codePointAt(0);
|
|
1374
|
+
if (cp >= 0xd800 && cp <= 0xdfff) { decodeSize = 1; return 0xfffd; }
|
|
1375
|
+
// Size is in UTF-16 code units, matching the JS backend (strings are JS strings).
|
|
1376
|
+
decodeSize = cp > 0xffff ? 2 : 1;
|
|
1377
|
+
return cp;
|
|
1378
|
+
},
|
|
1379
|
+
"utf8_decode_size": () => decodeSize,
|
|
1380
|
+
"utf8_full_rune": (s) => { s = s ?? ""; if (s.length === 0) return 0; const c = s.charCodeAt(0); return (c >= 0xd800 && c <= 0xdbff && s.length < 2) ? 0 : 1; },
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
function ${P}trim(s, cutset, left, right) {
|
|
1384
|
+
const cs = Array.from(cutset);
|
|
1385
|
+
const chars = Array.from(s);
|
|
1386
|
+
let a = 0, b = chars.length;
|
|
1387
|
+
if (left) while (a < b && cs.includes(chars[a])) a++;
|
|
1388
|
+
if (right) while (b > a && cs.includes(chars[b - 1])) b--;
|
|
1389
|
+
return chars.slice(a, b).join("");
|
|
1034
1390
|
}`;
|
|
1035
1391
|
|
|
1036
1392
|
// Runtime loader emitted at the top of the facade.
|
|
1037
|
-
|
|
1393
|
+
const wasmLoaderJs = (stdlibEnv) => `${WASM_IMPORTS_JS}
|
|
1394
|
+
${stdlibEnv ? WASM_STDLIB_JS : ""}
|
|
1038
1395
|
async function ${P}fetch(url) {
|
|
1039
1396
|
const res = await fetch(url);
|
|
1040
1397
|
if (!res.ok) throw new Error("GoFront: failed to fetch " + url + " (" + res.status + ")");
|
|
@@ -1043,7 +1400,8 @@ async function ${P}fetch(url) {
|
|
|
1043
1400
|
async function ${P}instantiate(imports) {
|
|
1044
1401
|
const bytes = globalThis.__GOFRONT_WASM_BYTES;
|
|
1045
1402
|
if (bytes) return WebAssembly.instantiate(bytes, imports);
|
|
1046
|
-
|
|
1403
|
+
// Resolve relative to the bundle so the app works from any page path.
|
|
1404
|
+
const url = globalThis.__GOFRONT_WASM_URL ?? new URL("app.wasm", import.meta.url).href;
|
|
1047
1405
|
if (typeof WebAssembly.instantiateStreaming === "function") {
|
|
1048
1406
|
try { return await WebAssembly.instantiateStreaming(fetch(url), imports); }
|
|
1049
1407
|
catch { /* fall through: wrong MIME type or no streaming support */ }
|
|
@@ -1060,6 +1418,7 @@ async function ${P}load(stringTable, extraEnv) {
|
|
|
1060
1418
|
// start function while package-level initializers run), so exports are
|
|
1061
1419
|
// returned raw: no try/catch wrapper, which keeps JS→wasm calls inlinable.
|
|
1062
1420
|
const result = await ${P}instantiate(imports);
|
|
1421
|
+
imports.__bind(result.instance.exports);
|
|
1063
1422
|
return result.instance.exports;
|
|
1064
1423
|
}
|
|
1065
1424
|
// Go int/int64 cross the boundary as f64 (exact within the safe-integer range).
|
|
@@ -1074,6 +1433,11 @@ const ${P}u64out = (v) => {
|
|
|
1074
1433
|
};
|
|
1075
1434
|
const ${P}strin = (v) => (v == null ? "" : String(v));
|
|
1076
1435
|
const ${P}href = (h) => (h == null ? null : h.__ref);
|
|
1436
|
+
// Tags a struct pointer held in an interface (same marker as the JS backend's __ifp).
|
|
1437
|
+
const ${P}ptag = (h) => {
|
|
1438
|
+
if (h !== null && h.__p !== true && Object.isExtensible(h)) Object.defineProperty(h, "__p", { "value": true, "configurable": true });
|
|
1439
|
+
return h;
|
|
1440
|
+
};
|
|
1077
1441
|
const ${P}NIL_DEREF_PATTERNS = [
|
|
1078
1442
|
"dereferencing a null pointer", // V8
|
|
1079
1443
|
"dereferencing null pointer", // SpiderMonkey
|
|
@@ -1110,16 +1474,19 @@ function ${P}idxview(n, getAt, setAt) {
|
|
|
1110
1474
|
// Generates the JS facade for the linked wasm module.
|
|
1111
1475
|
export function generateFacade(
|
|
1112
1476
|
meta,
|
|
1113
|
-
{ stringTable = [], callMain = false } = {},
|
|
1477
|
+
{ stringTable = [], callMain = false, stdlibEnv = true } = {},
|
|
1114
1478
|
) {
|
|
1115
1479
|
const needs = meta._needs ?? collectNeeds(meta);
|
|
1116
|
-
const out = [
|
|
1480
|
+
const out = [wasmLoaderJs(stdlibEnv)];
|
|
1117
1481
|
|
|
1118
1482
|
// JS callback imports (registered before instantiation; bodies run after).
|
|
1119
1483
|
out.push(`const ${P}env = {};`);
|
|
1120
1484
|
for (const [key, desc] of needs.funcs) {
|
|
1121
1485
|
const names = desc.params.map((_, i) => `a${i}`);
|
|
1122
|
-
const
|
|
1486
|
+
const args = desc.params.map((d, i) =>
|
|
1487
|
+
isPtrBoth(d) ? `${P}view_${d.name}(${names[i]})` : outExpr(d, names[i]),
|
|
1488
|
+
);
|
|
1489
|
+
const call = `fn(${args.join(", ")})`;
|
|
1123
1490
|
let body;
|
|
1124
1491
|
if (desc.returns.length === 0) body = `${call};`;
|
|
1125
1492
|
else if (desc.returns.length === 1)
|
|
@@ -1129,7 +1496,9 @@ export function generateFacade(
|
|
|
1129
1496
|
.map((d, i) => inExpr(d, `__r[${i}]`))
|
|
1130
1497
|
.join(", ")}];`;
|
|
1131
1498
|
const sig = ["fn", ...names].join(", ");
|
|
1132
|
-
out.push(
|
|
1499
|
+
out.push(
|
|
1500
|
+
`${P}env["__invoke$${key}"] = (${sig}) => { try { ${body} } catch (__e) { if (__w && __w.__push_panic) __w.__push_panic(__e.message ?? String(__e)); throw __e; } };`,
|
|
1501
|
+
);
|
|
1133
1502
|
}
|
|
1134
1503
|
out.push(
|
|
1135
1504
|
`const __w = await ${P}load(${JSON.stringify(stringTable)}, ${P}env);`,
|
|
@@ -1196,22 +1565,61 @@ function ${P}arrview_${key}(a) {
|
|
|
1196
1565
|
desc.returns,
|
|
1197
1566
|
["c"],
|
|
1198
1567
|
);
|
|
1199
|
-
out.push(`const ${P}
|
|
1568
|
+
out.push(`const ${P}fn_j2w_${key} = new WeakMap();
|
|
1569
|
+
const ${P}fn_w2j_${key} = new WeakMap();
|
|
1200
1570
|
function ${P}fnin_${key}(fn) {
|
|
1201
1571
|
if (fn == null) return null;
|
|
1202
|
-
let c = ${P}
|
|
1203
|
-
if (!c) {
|
|
1572
|
+
let c = ${P}fn_j2w_${key}.get(fn);
|
|
1573
|
+
if (!c) {
|
|
1574
|
+
c = __w.__wrap_fn$${key}(fn);
|
|
1575
|
+
${P}fn_j2w_${key}.set(fn, c);
|
|
1576
|
+
${P}fn_w2j_${key}.set(c, fn);
|
|
1577
|
+
}
|
|
1204
1578
|
return c;
|
|
1205
1579
|
}
|
|
1206
1580
|
function ${P}fnout_${key}(c) {
|
|
1207
1581
|
if (c == null) return null;
|
|
1208
|
-
|
|
1582
|
+
let fn = ${P}fn_w2j_${key}.get(c);
|
|
1583
|
+
if (!fn) {
|
|
1584
|
+
fn = (${names.join(", ")}) => {
|
|
1209
1585
|
${indent(body, "\t")}
|
|
1210
|
-
|
|
1586
|
+
};
|
|
1587
|
+
fn.__ref = c;
|
|
1588
|
+
${P}fn_w2j_${key}.set(c, fn);
|
|
1589
|
+
${P}fn_j2w_${key}.set(fn, c);
|
|
1590
|
+
}
|
|
1591
|
+
return fn;
|
|
1592
|
+
}`);
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
for (const [key, desc] of needs.ifaces) genIfaceFacade(out, key, desc);
|
|
1596
|
+
|
|
1597
|
+
if (needs.shared) {
|
|
1598
|
+
// Zero-copy TypedArray views over the exported memory. Memory never
|
|
1599
|
+
// grows after the start function (allocation is startup-only), but the
|
|
1600
|
+
// cached view is still re-validated against the live buffer.
|
|
1601
|
+
out.push(`const ${P}shc = new WeakMap();
|
|
1602
|
+
function ${P}shout(r, Ctor) {
|
|
1603
|
+
if (r == null) return null;
|
|
1604
|
+
let v = ${P}shc.get(r);
|
|
1605
|
+
if (!v || v.buffer !== __w.memory.buffer) {
|
|
1606
|
+
v = new Ctor(__w.memory.buffer, __w.__shared_base(r), __w.__shared_len(r));
|
|
1607
|
+
${P}shc.set(r, v);
|
|
1608
|
+
}
|
|
1609
|
+
return v;
|
|
1610
|
+
}
|
|
1611
|
+
function ${P}shin(v, Ctor) {
|
|
1612
|
+
if (v == null) return null;
|
|
1613
|
+
if (!(v instanceof Ctor) || v.buffer !== __w.memory.buffer) throw new TypeError("GoFront: expected a " + Ctor.name + " view over the wasm shared memory");
|
|
1614
|
+
return __w.__shared_make(v.byteOffset, v.length);
|
|
1211
1615
|
}`);
|
|
1212
1616
|
}
|
|
1213
1617
|
|
|
1214
1618
|
for (const c of meta.consts) out.push(`const ${c.name} = ${c.value};`);
|
|
1619
|
+
for (const v of meta.vars ?? [])
|
|
1620
|
+
out.push(
|
|
1621
|
+
`const ${v.name} = ${P}shout(__w.__var_${v.name}(), ${v.desc.ctor});`,
|
|
1622
|
+
);
|
|
1215
1623
|
|
|
1216
1624
|
for (const f of meta.funcs) {
|
|
1217
1625
|
const { names, body } = genFuncBody(
|
|
@@ -1298,21 +1706,66 @@ ${backLines.join("\n")}
|
|
|
1298
1706
|
}`);
|
|
1299
1707
|
}
|
|
1300
1708
|
|
|
1709
|
+
function genIfaceFacade(out, key, desc) {
|
|
1710
|
+
const methods = desc.methods.map((m) => {
|
|
1711
|
+
const { names, body } = genFuncBody(m.callName, m.params, m.returns, [
|
|
1712
|
+
"this.__ref",
|
|
1713
|
+
]);
|
|
1714
|
+
return `\t${m.name}(${names.join(", ")}) {\n${indent(body, "\t")}\n\t}`;
|
|
1715
|
+
});
|
|
1716
|
+
const cases = desc.concrete.map((c, i) => {
|
|
1717
|
+
const wrap = c.both ? `${P}view_${c.name}(r)` : `${c.name}.__wrap(r)`;
|
|
1718
|
+
return `\t\tcase ${i + 1}: return ${P}ptag(${wrap});`;
|
|
1719
|
+
});
|
|
1720
|
+
const tag =
|
|
1721
|
+
cases.length > 0
|
|
1722
|
+
? `\tswitch (__w.__itag$${key}(r)) {\n${cases.join("\n")}\n\t}\n`
|
|
1723
|
+
: "";
|
|
1724
|
+
const msg = `GoFront: a JS-implemented value cannot cross into wasm as interface '${desc.name}'; implement it in a wasm package or pass a func callback`;
|
|
1725
|
+
const implMsg = `GoFront: wasm value does not implement interface '${desc.name}'`;
|
|
1726
|
+
out.push(`class ${P}I_${key} {
|
|
1727
|
+
constructor(r) { this.__ref = r; }
|
|
1728
|
+
${methods.join("\n")}
|
|
1729
|
+
}
|
|
1730
|
+
const ${P}ic_${key} = new WeakMap();
|
|
1731
|
+
function ${P}ifout_${key}(r) {
|
|
1732
|
+
if (r == null) return null;
|
|
1733
|
+
${tag} let o = ${P}ic_${key}.get(r);
|
|
1734
|
+
if (!o) { o = new ${P}I_${key}(r); ${P}ic_${key}.set(r, o); }
|
|
1735
|
+
return o;
|
|
1736
|
+
}
|
|
1737
|
+
function ${P}ifin_${key}(v) {
|
|
1738
|
+
if (v == null) return null;
|
|
1739
|
+
if (v.__ref === undefined) throw new TypeError(${JSON.stringify(msg)});
|
|
1740
|
+
if (!__w.__impl$${key}(v.__ref)) throw new TypeError(${JSON.stringify(implMsg)});
|
|
1741
|
+
return v.__ref;
|
|
1742
|
+
}`);
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1301
1745
|
function genHandleStruct(out, name, fields, methods) {
|
|
1302
1746
|
// `$`-suffixed params keep the mangler from renaming the matching getters.
|
|
1303
1747
|
const ctorParams = fields.map((f) => `${f.name}$ = ${zeroExpr(f.desc)}`);
|
|
1304
1748
|
const ctorArgs = fields.map((f) => inExpr(f.desc, `${f.name}$`));
|
|
1749
|
+
const fromArgs = fields.map((f) => `o.${f.name}`);
|
|
1750
|
+
const ptrMsg = `GoFront: expected a ${name} handle for a *${name} parameter (use ${name}.from(obj) to create one)`;
|
|
1305
1751
|
const lines = [
|
|
1306
1752
|
`const ${P}h_${name} = new WeakMap();
|
|
1307
1753
|
function ${P}hval_${name}(h) {
|
|
1308
1754
|
if (h == null) return __w.__zero_${name}();
|
|
1309
|
-
return __w.__clone_${name}(h.__ref);
|
|
1755
|
+
if (h.__ref !== undefined) return __w.__clone_${name}(h.__ref);
|
|
1756
|
+
return ${name}.from(h).__ref;
|
|
1757
|
+
}
|
|
1758
|
+
function ${P}href_${name}(h) {
|
|
1759
|
+
if (h == null) return null;
|
|
1760
|
+
if (h.__ref !== undefined) return h.__ref;
|
|
1761
|
+
throw new TypeError(${JSON.stringify(ptrMsg)});
|
|
1310
1762
|
}
|
|
1311
1763
|
class ${name} {
|
|
1312
1764
|
constructor(${ctorParams.join(", ")}) {
|
|
1313
1765
|
this.__ref = __w.__new_${name}(${ctorArgs.join(", ")});
|
|
1314
1766
|
${P}h_${name}.set(this.__ref, this);
|
|
1315
1767
|
}
|
|
1768
|
+
static from(o) { return new ${name}(${fromArgs.join(", ")}); }
|
|
1316
1769
|
static __wrap(ref) {
|
|
1317
1770
|
if (ref == null) return null;
|
|
1318
1771
|
let h = ${P}h_${name}.get(ref);
|