@thi.ng/wasm-api 0.7.0 → 0.10.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/CHANGELOG.md +35 -1
- package/README.md +143 -64
- package/api.d.ts +61 -18
- package/api.js +1 -0
- package/bridge.d.ts +27 -7
- package/bridge.js +48 -10
- package/cli.js +2 -2
- package/codegen/typescript.d.ts +9 -0
- package/codegen/typescript.js +53 -17
- package/codegen/utils.d.ts +5 -4
- package/codegen/utils.js +6 -5
- package/codegen/zig.d.ts +3 -0
- package/codegen/zig.js +5 -6
- package/codegen.d.ts +38 -1
- package/codegen.js +48 -16
- package/include/wasmapi.h +12 -5
- package/include/wasmapi.zig +22 -19
- package/index.d.ts +2 -2
- package/index.js +2 -2
- package/package.json +17 -11
package/bridge.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { __decorate } from "tslib";
|
|
2
|
+
import { INotifyMixin } from "@thi.ng/api/mixins/inotify";
|
|
1
3
|
import { defError } from "@thi.ng/errors/deferror";
|
|
2
4
|
import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
|
|
3
5
|
import { U16, U32, U64HL, U8 } from "@thi.ng/hex";
|
|
4
6
|
import { ConsoleLogger } from "@thi.ng/logger/console";
|
|
7
|
+
import { EVENT_MEMORY_CHANGED, } from "./api.js";
|
|
5
8
|
const B32 = BigInt(32);
|
|
6
9
|
export const OutOfMemoryError = defError(() => "Out of memory");
|
|
7
10
|
/**
|
|
@@ -20,7 +23,7 @@ export const OutOfMemoryError = defError(() => "Out of memory");
|
|
|
20
23
|
* 64bit integers are handled via JS `BigInt` and hence require the host env to
|
|
21
24
|
* support it. No polyfill is provided.
|
|
22
25
|
*/
|
|
23
|
-
|
|
26
|
+
let WasmBridge = class WasmBridge {
|
|
24
27
|
constructor(modules = {}, logger = new ConsoleLogger("wasm")) {
|
|
25
28
|
this.modules = modules;
|
|
26
29
|
this.logger = logger;
|
|
@@ -55,6 +58,9 @@ export class WasmBridge {
|
|
|
55
58
|
_printF64Array: logA(this.getF64Array.bind(this)),
|
|
56
59
|
_printStr0: (addr) => this.logger.debug(this.getString(addr, 0)),
|
|
57
60
|
_printStr: (addr, len) => this.logger.debug(this.getString(addr, len)),
|
|
61
|
+
debug: () => {
|
|
62
|
+
debugger;
|
|
63
|
+
},
|
|
58
64
|
};
|
|
59
65
|
}
|
|
60
66
|
/**
|
|
@@ -83,24 +89,34 @@ export class WasmBridge {
|
|
|
83
89
|
* then initializes all declared bridge child API modules. Returns false if
|
|
84
90
|
* any of the module initializations failed.
|
|
85
91
|
*
|
|
92
|
+
* @remarks
|
|
93
|
+
* Emits the {@link EVENT_MEMORY_CHANGED} event just before returning (and
|
|
94
|
+
* AFTER all child API modules have been initialized).
|
|
95
|
+
*
|
|
86
96
|
* @param exports
|
|
87
97
|
*/
|
|
88
98
|
async init(exports) {
|
|
89
99
|
this.exports = exports;
|
|
90
|
-
this.ensureMemory();
|
|
100
|
+
this.ensureMemory(false);
|
|
91
101
|
for (let id in this.modules) {
|
|
92
102
|
this.logger.debug(`initializing API module: ${id}`);
|
|
93
103
|
const status = await this.modules[id].init(this);
|
|
94
104
|
if (!status)
|
|
95
105
|
return false;
|
|
96
106
|
}
|
|
107
|
+
this.notify({ id: EVENT_MEMORY_CHANGED, value: this.exports.memory });
|
|
97
108
|
return true;
|
|
98
109
|
}
|
|
99
110
|
/**
|
|
100
|
-
* Called automatically. Initializes and/or updates
|
|
101
|
-
* memory views (e.g. after growing the WASM memory
|
|
111
|
+
* Called automatically during initialization. Initializes and/or updates
|
|
112
|
+
* the various typed WASM memory views (e.g. after growing the WASM memory
|
|
113
|
+
* and the previous buffer becoming detached). Unless `notify` is false,
|
|
114
|
+
* the {@link EVENT_MEMORY_CHANGED} event will be emitted if the memory
|
|
115
|
+
* views had to be updated.
|
|
116
|
+
*
|
|
117
|
+
* @param notify
|
|
102
118
|
*/
|
|
103
|
-
ensureMemory() {
|
|
119
|
+
ensureMemory(notify = true) {
|
|
104
120
|
const buf = this.exports.memory.buffer;
|
|
105
121
|
if (this.u8 && this.u8.buffer === buf)
|
|
106
122
|
return;
|
|
@@ -114,6 +130,11 @@ export class WasmBridge {
|
|
|
114
130
|
this.u64 = new BigUint64Array(buf);
|
|
115
131
|
this.f32 = new Float32Array(buf);
|
|
116
132
|
this.f64 = new Float64Array(buf);
|
|
133
|
+
notify &&
|
|
134
|
+
this.notify({
|
|
135
|
+
id: EVENT_MEMORY_CHANGED,
|
|
136
|
+
value: this.exports.memory,
|
|
137
|
+
});
|
|
117
138
|
}
|
|
118
139
|
/**
|
|
119
140
|
* Required use for WASM module instantiation to provide JS imports to the
|
|
@@ -189,7 +210,7 @@ export class WasmBridge {
|
|
|
189
210
|
const addr = this.exports._wasm_allocate(numBytes);
|
|
190
211
|
if (!addr)
|
|
191
212
|
throw new OutOfMemoryError(`unable to allocate: ${numBytes}`);
|
|
192
|
-
this.logger.debug(`allocated ${numBytes} bytes @ 0x${U32(addr)}`);
|
|
213
|
+
this.logger.debug(`allocated ${numBytes} bytes @ 0x${U32(addr)} .. 0x${U32(addr + numBytes - 1)}`);
|
|
193
214
|
this.ensureMemory();
|
|
194
215
|
clear && this.u8.fill(0, addr, addr + numBytes);
|
|
195
216
|
return addr;
|
|
@@ -200,6 +221,10 @@ export class WasmBridge {
|
|
|
200
221
|
* `numBytes` value must be the same as previously given to
|
|
201
222
|
* {@link WasmBridge.allocate}.
|
|
202
223
|
*
|
|
224
|
+
* @remarks
|
|
225
|
+
* This function always succeeds, regardless of presence of an active
|
|
226
|
+
* allocator on the WASM side or validity of given arguments.
|
|
227
|
+
*
|
|
203
228
|
* @param addr
|
|
204
229
|
* @param numBytes
|
|
205
230
|
*/
|
|
@@ -370,7 +395,8 @@ export class WasmBridge {
|
|
|
370
395
|
/**
|
|
371
396
|
* Encodes given string as UTF-8 and writes it to WASM memory starting at
|
|
372
397
|
* `addr`. By default the string will be zero-terminated and only `maxBytes`
|
|
373
|
-
* will be written. Returns the number of bytes written
|
|
398
|
+
* will be written. Returns the number of bytes written (excluding final
|
|
399
|
+
* sentinel, if any).
|
|
374
400
|
*
|
|
375
401
|
* @remarks
|
|
376
402
|
* An error will be thrown if the encoded string doesn't fully fit into the
|
|
@@ -386,11 +412,10 @@ export class WasmBridge {
|
|
|
386
412
|
maxBytes = Math.min(maxBytes, this.u8.length - addr);
|
|
387
413
|
const len = this.utf8Encoder.encodeInto(str, this.u8.subarray(addr, addr + maxBytes)).written;
|
|
388
414
|
if (len == null || len >= maxBytes + (terminate ? 0 : 1)) {
|
|
389
|
-
illegalArgs(`error writing string to 0x${U32(addr)}`);
|
|
415
|
+
illegalArgs(`error writing string to 0x${U32(addr)} (max. ${maxBytes} bytes, got at least ${str.length})`);
|
|
390
416
|
}
|
|
391
417
|
if (terminate) {
|
|
392
418
|
this.u8[addr + len] = 0;
|
|
393
|
-
return len + 1;
|
|
394
419
|
}
|
|
395
420
|
return len;
|
|
396
421
|
}
|
|
@@ -400,4 +425,17 @@ export class WasmBridge {
|
|
|
400
425
|
el == null && illegalArgs(`missing DOM element #${id}`);
|
|
401
426
|
return el;
|
|
402
427
|
}
|
|
403
|
-
}
|
|
428
|
+
/** {@inheritDoc @thi.ng/api#INotify.addListener} */
|
|
429
|
+
// @ts-ignore: mixin
|
|
430
|
+
addListener(id, fn, scope) { }
|
|
431
|
+
/** {@inheritDoc @thi.ng/api#INotify.removeListener} */
|
|
432
|
+
// @ts-ignore: mixin
|
|
433
|
+
removeListener(id, fn, scope) { }
|
|
434
|
+
/** {@inheritDoc @thi.ng/api#INotify.notify} */
|
|
435
|
+
// @ts-ignore: mixin
|
|
436
|
+
notify(event) { }
|
|
437
|
+
};
|
|
438
|
+
WasmBridge = __decorate([
|
|
439
|
+
INotifyMixin
|
|
440
|
+
], WasmBridge);
|
|
441
|
+
export { WasmBridge };
|
package/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import { ConsoleLogger } from "@thi.ng/logger";
|
|
|
6
6
|
import { resolve } from "path";
|
|
7
7
|
import { generateTypes } from "./codegen.js";
|
|
8
8
|
import { TYPESCRIPT } from "./codegen/typescript.js";
|
|
9
|
-
import {
|
|
9
|
+
import { isWasmPrim, isWasmString } from "./codegen/utils.js";
|
|
10
10
|
import { ZIG } from "./codegen/zig.js";
|
|
11
11
|
const GENERATORS = { ts: TYPESCRIPT, zig: ZIG };
|
|
12
12
|
const argOpts = {
|
|
@@ -72,7 +72,7 @@ const validateTypeRefs = (coll) => {
|
|
|
72
72
|
if (spec.type !== "struct")
|
|
73
73
|
continue;
|
|
74
74
|
for (let f of spec.fields) {
|
|
75
|
-
if (!(
|
|
75
|
+
if (!(isWasmPrim(f.type) || isWasmString(f.type) || coll[f.type])) {
|
|
76
76
|
invalidSpec(spec.__path, `structfield ${spec.name}.${f.name} of unknown type: ${f.type}`);
|
|
77
77
|
}
|
|
78
78
|
}
|
package/codegen/typescript.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { ICodeGen } from "../api.js";
|
|
2
|
+
/**
|
|
3
|
+
* TypeScript code generator options.
|
|
4
|
+
*/
|
|
2
5
|
export interface TSOpts {
|
|
3
6
|
/**
|
|
4
7
|
* Indentation string
|
|
@@ -8,8 +11,14 @@ export interface TSOpts {
|
|
|
8
11
|
indent: string;
|
|
9
12
|
/**
|
|
10
13
|
* If true (default), forces uppercase enums
|
|
14
|
+
*
|
|
15
|
+
* @defaultValue true
|
|
11
16
|
*/
|
|
12
17
|
uppercaseEnums: boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Same as {@link CodeGenOpts.stringType}.
|
|
20
|
+
*/
|
|
21
|
+
stringType: "slice" | "ptr";
|
|
13
22
|
}
|
|
14
23
|
/**
|
|
15
24
|
* TypeScript code generator. Call with options and then pass to
|
package/codegen/typescript.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BIGINT_ARRAY_CTORS, BIT_SHIFTS, TYPEDARRAY_CTORS, } from "@thi.ng/api/typedarray";
|
|
2
2
|
import { isString } from "@thi.ng/checks/is-string";
|
|
3
|
-
import { PKG_NAME, USIZE, } from "../api.js";
|
|
4
|
-
import { isBigNumeric, isNumeric,
|
|
3
|
+
import { PKG_NAME, USIZE, USIZE_SIZE, } from "../api.js";
|
|
4
|
+
import { isBigNumeric, isNumeric, isWasmPrim, isWasmString, prefixLines, } from "./utils.js";
|
|
5
5
|
/**
|
|
6
6
|
* TypeScript code generator. Call with options and then pass to
|
|
7
7
|
* {@link generateTypes} (see its docs for further usage).
|
|
@@ -14,8 +14,9 @@ import { isBigNumeric, isNumeric, isPrim, prefixLines } from "./utils.js";
|
|
|
14
14
|
* @param opts
|
|
15
15
|
*/
|
|
16
16
|
export const TYPESCRIPT = (opts) => {
|
|
17
|
-
const { indent, uppercaseEnums } = {
|
|
17
|
+
const { indent, stringType, uppercaseEnums } = {
|
|
18
18
|
indent: "\t",
|
|
19
|
+
stringType: "slice",
|
|
19
20
|
uppercaseEnums: true,
|
|
20
21
|
...opts,
|
|
21
22
|
};
|
|
@@ -36,7 +37,7 @@ export const TYPESCRIPT = (opts) => {
|
|
|
36
37
|
const e = type;
|
|
37
38
|
acc.push(`export enum ${e.name} {`);
|
|
38
39
|
for (let v of e.values) {
|
|
39
|
-
|
|
40
|
+
let line = indent;
|
|
40
41
|
if (!isString(v)) {
|
|
41
42
|
v.doc && gen.doc(v.doc, indent, acc);
|
|
42
43
|
line += uppercaseEnums ? v.name.toUpperCase() : v.name;
|
|
@@ -83,31 +84,60 @@ export const TYPESCRIPT = (opts) => {
|
|
|
83
84
|
for (let f of struct.fields) {
|
|
84
85
|
const offset = f.__offset || 0;
|
|
85
86
|
acc.push(`${I2}get ${f.name}(): ${returnTypes[f.name]} {`);
|
|
86
|
-
const
|
|
87
|
+
const isPrim = isWasmPrim(f.type);
|
|
88
|
+
const isStr = isWasmString(f.type);
|
|
87
89
|
if (f.tag === "ptr") {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
90
|
+
if (isPrim) {
|
|
91
|
+
acc.push(`${I3}return mem.${f.type}[${__ptrShift(offset, f.type)}];`);
|
|
92
|
+
}
|
|
93
|
+
else if (isStr) {
|
|
94
|
+
acc.push(
|
|
95
|
+
// double deref
|
|
96
|
+
stringType === "slice"
|
|
97
|
+
? `${I3}return mem.getString(mem.${USIZE}[${__ptr(offset)} >>> ${USIZE_SIZE}])`
|
|
98
|
+
: `${I3}return mem.getString(${__ptr(offset)})`);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
acc.push(`${I3}return $${f.type}.instance(${__ptr(offset)});`);
|
|
102
|
+
}
|
|
91
103
|
}
|
|
92
104
|
else if (f.tag === "slice") {
|
|
93
|
-
acc.push(`${I3}const len = ${__ptr(offset + 4)}
|
|
94
|
-
|
|
95
|
-
${I3}return mem.${f.type}.subarray(addr, addr + len);`
|
|
96
|
-
|
|
105
|
+
acc.push(`${I3}const len = ${__ptr(offset + 4)};`);
|
|
106
|
+
if (isPrim) {
|
|
107
|
+
acc.push(`${I3}const addr = ${__ptrShift(offset, f.type)};`, `${I3}return mem.${f.type}.subarray(addr, addr + len);`);
|
|
108
|
+
}
|
|
109
|
+
else if (isStr) {
|
|
110
|
+
acc.push(`${I3}const addr = ${__ptr(offset)};`, __mapStringArray(I3, stringType));
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
acc.push(`${I3}const addr = ${__ptr(offset)};`, __mapArray(f, I3));
|
|
114
|
+
}
|
|
97
115
|
}
|
|
98
116
|
else if (f.tag === "array" || f.tag === "vec") {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
117
|
+
if (isPrim) {
|
|
118
|
+
acc.push(`${I3}const addr = ${__addrShift(offset, f.type)};`, `${I3}return mem.${f.type}.subarray(addr, addr + ${f.len});`);
|
|
119
|
+
}
|
|
120
|
+
else if (isStr) {
|
|
121
|
+
acc.push(`${I3}const addr = ${__addr(offset)};`, __mapStringArray(I3, stringType, f.len));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
acc.push(`${I3}const addr = ${__addr(offset)};`, __mapArray(f, I3, f.len));
|
|
125
|
+
}
|
|
103
126
|
}
|
|
104
127
|
else {
|
|
105
128
|
let setter;
|
|
106
|
-
if (
|
|
129
|
+
if (isPrim) {
|
|
107
130
|
const addr = __mem(f.type, f.__offset);
|
|
108
131
|
acc.push(`${I3}return ${addr};`);
|
|
109
132
|
setter = `${addr} = x`;
|
|
110
133
|
}
|
|
134
|
+
else if (isStr) {
|
|
135
|
+
acc.push(`${I3}return mem.getString(${__ptr(offset)})`);
|
|
136
|
+
setter =
|
|
137
|
+
stringType === "slice"
|
|
138
|
+
? `mem.setString(x, ${__ptr(offset)}, ${__ptr(offset + 4)} + 1, true);`
|
|
139
|
+
: `throw new Error("unsupported for raw string pointers")`;
|
|
140
|
+
}
|
|
111
141
|
else if (types[f.type].type === "enum") {
|
|
112
142
|
const tag = types[f.type].tag;
|
|
113
143
|
const addr = __mem(tag, f.__offset);
|
|
@@ -151,3 +181,9 @@ const __mapArray = (f, indent, len = "len") => prefixLines(indent, `const inst =
|
|
|
151
181
|
const slice: ${f.type}[] = [];
|
|
152
182
|
for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${f.__size}));
|
|
153
183
|
return slice;`);
|
|
184
|
+
/** @internal */
|
|
185
|
+
const __mapStringArray = (indent, type, len = "len") => prefixLines(indent, [
|
|
186
|
+
"const slice: string[] = [];",
|
|
187
|
+
`for(let i = 0; i < ${len}; i++) slice.push(mem.getString(mem.${USIZE}[(addr + i * ${USIZE_SIZE * (type === "slice" ? 2 : 1)}) >>> ${__shift(USIZE)}]));`,
|
|
188
|
+
"return slice;",
|
|
189
|
+
]);
|
package/codegen/utils.d.ts
CHANGED
|
@@ -17,13 +17,14 @@ export declare const isBigNumeric: (x: string) => x is BigType;
|
|
|
17
17
|
*
|
|
18
18
|
* @param x
|
|
19
19
|
*/
|
|
20
|
-
export declare const
|
|
20
|
+
export declare const isWasmPrim: (x: string) => x is WasmPrim;
|
|
21
|
+
export declare const isWasmString: (x: string) => x is "string";
|
|
21
22
|
/**
|
|
22
|
-
*
|
|
23
|
-
* returns rejoined result.
|
|
23
|
+
* Takes an array of strings or splits given string into lines, prefixes each
|
|
24
|
+
* line with given `prefix` and then returns rejoined result.
|
|
24
25
|
*
|
|
25
26
|
* @param prefix
|
|
26
27
|
* @param str
|
|
27
28
|
*/
|
|
28
|
-
export declare const prefixLines: (prefix: string, str: string) => string;
|
|
29
|
+
export declare const prefixLines: (prefix: string, str: string | string[]) => string;
|
|
29
30
|
//# sourceMappingURL=utils.d.ts.map
|
package/codegen/utils.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isString } from "@thi.ng/checks/is-string";
|
|
1
2
|
/**
|
|
2
3
|
* Returns true iff `x` is a {@link WasmPrim32}.
|
|
3
4
|
*
|
|
@@ -15,15 +16,15 @@ export const isBigNumeric = (x) => /^[iu]64$/.test(x);
|
|
|
15
16
|
*
|
|
16
17
|
* @param x
|
|
17
18
|
*/
|
|
18
|
-
export const
|
|
19
|
+
export const isWasmPrim = (x) => isNumeric(x) || isBigNumeric(x);
|
|
20
|
+
export const isWasmString = (x) => x === "string";
|
|
19
21
|
/**
|
|
20
|
-
*
|
|
21
|
-
* returns rejoined result.
|
|
22
|
+
* Takes an array of strings or splits given string into lines, prefixes each
|
|
23
|
+
* line with given `prefix` and then returns rejoined result.
|
|
22
24
|
*
|
|
23
25
|
* @param prefix
|
|
24
26
|
* @param str
|
|
25
27
|
*/
|
|
26
|
-
export const prefixLines = (prefix, str) => str
|
|
27
|
-
.split("\n")
|
|
28
|
+
export const prefixLines = (prefix, str) => (isString(str) ? str.split("\n") : str)
|
|
28
29
|
.map((line) => prefix + line)
|
|
29
30
|
.join("\n");
|
package/codegen/zig.d.ts
CHANGED
package/codegen/zig.js
CHANGED
|
@@ -39,23 +39,22 @@ export const ZIG = (opts) => {
|
|
|
39
39
|
const ftypes = {};
|
|
40
40
|
for (let f of struct.fields) {
|
|
41
41
|
f.doc && gen.doc(f.doc, " ", acc);
|
|
42
|
-
|
|
42
|
+
let ftype = f.type === "string" ? "[]const u8" : f.type;
|
|
43
43
|
switch (f.tag) {
|
|
44
44
|
case "array":
|
|
45
|
-
ftype = `[${f.len}]${
|
|
45
|
+
ftype = `[${f.len}]${ftype}`;
|
|
46
46
|
break;
|
|
47
47
|
case "slice":
|
|
48
|
-
ftype = `[]${
|
|
48
|
+
ftype = `[]${ftype}`;
|
|
49
49
|
break;
|
|
50
50
|
case "vec":
|
|
51
|
-
ftype = `@Vector(${f.len}, ${
|
|
51
|
+
ftype = `@Vector(${f.len}, ${ftype})`;
|
|
52
52
|
break;
|
|
53
53
|
case "ptr":
|
|
54
|
-
ftype = `*${f.len ? `[${f.len}]` : ""}${
|
|
54
|
+
ftype = `*${f.len ? `[${f.len}]` : ""}${ftype}`;
|
|
55
55
|
break;
|
|
56
56
|
case "scalar":
|
|
57
57
|
default:
|
|
58
|
-
ftype = f.type;
|
|
59
58
|
}
|
|
60
59
|
ftypes[f.name] = ftype;
|
|
61
60
|
acc.push(` ${f.name}: ${ftype},`);
|
package/codegen.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { ICodeGen, TypeColl } from "./api.js";
|
|
2
|
+
/**
|
|
3
|
+
* Global/shared code generator options.
|
|
4
|
+
*/
|
|
2
5
|
export interface CodeGenOpts {
|
|
3
6
|
/**
|
|
4
7
|
* Optional string to be injected before generated type defs (but after
|
|
@@ -10,7 +13,41 @@ export interface CodeGenOpts {
|
|
|
10
13
|
* codegen's own epilogue, if any)
|
|
11
14
|
*/
|
|
12
15
|
post: string;
|
|
16
|
+
/**
|
|
17
|
+
* Identifier how strings are stored on WASM side, e.g. in Zig string
|
|
18
|
+
* literals are slices (8 bytes), in C just plain pointers (4 bytes).
|
|
19
|
+
*
|
|
20
|
+
* @defaultValue "slice"
|
|
21
|
+
*/
|
|
22
|
+
stringType: "slice" | "ptr";
|
|
13
23
|
}
|
|
14
|
-
|
|
24
|
+
/**
|
|
25
|
+
* Takes a type collection and analyzes each analyzed to compute individual
|
|
26
|
+
* alignments and sizes.
|
|
27
|
+
*
|
|
28
|
+
* @remarks
|
|
29
|
+
* This function is idempotent and called automatically by
|
|
30
|
+
* {@link generateTypes}. Only exported for dev/debug purposes.
|
|
31
|
+
*
|
|
32
|
+
* @param types
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export declare const prepareTypes: (types: TypeColl, opts: CodeGenOpts) => void;
|
|
37
|
+
/**
|
|
38
|
+
* Code generator main entry point. Takes an object of {@link TopLevelType}
|
|
39
|
+
* definitions, an actual code generator implementation for a single target
|
|
40
|
+
* language and (optional) global codegen options. Returns generated source code
|
|
41
|
+
* for all given types as a single string.
|
|
42
|
+
*
|
|
43
|
+
* @remarks
|
|
44
|
+
* Before actual code generation the types are first analyzed to compute their
|
|
45
|
+
* alignments and sizes. This is only ever done once (idempotent), even if
|
|
46
|
+
* `generateTypes()` is called multiple times for different target langs.
|
|
47
|
+
*
|
|
48
|
+
* @param types
|
|
49
|
+
* @param codegen
|
|
50
|
+
* @param opts
|
|
51
|
+
*/
|
|
15
52
|
export declare const generateTypes: (types: TypeColl, codegen: ICodeGen, opts?: Partial<CodeGenOpts>) => string;
|
|
16
53
|
//# sourceMappingURL=codegen.d.ts.map
|
package/codegen.js
CHANGED
|
@@ -5,9 +5,9 @@ import { compareByKey } from "@thi.ng/compare/keys";
|
|
|
5
5
|
import { compareNumDesc } from "@thi.ng/compare/numeric";
|
|
6
6
|
import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti";
|
|
7
7
|
import { PKG_NAME, USIZE_SIZE, } from "./api.js";
|
|
8
|
-
import { isNumeric } from "./codegen/utils.js";
|
|
8
|
+
import { isNumeric, isWasmString } from "./codegen/utils.js";
|
|
9
9
|
const sizeOf = defmulti((x) => x.type, {}, {
|
|
10
|
-
[DEFAULT]: (field, types) => {
|
|
10
|
+
[DEFAULT]: (field, types, opts) => {
|
|
11
11
|
if (field.__size)
|
|
12
12
|
return field.__size;
|
|
13
13
|
let size = 0;
|
|
@@ -20,7 +20,9 @@ const sizeOf = defmulti((x) => x.type, {}, {
|
|
|
20
20
|
else {
|
|
21
21
|
size = isNumeric(field.type)
|
|
22
22
|
? SIZEOF[field.type]
|
|
23
|
-
:
|
|
23
|
+
: isWasmString(field.type)
|
|
24
|
+
? USIZE_SIZE * (opts.stringType === "slice" ? 2 : 1)
|
|
25
|
+
: sizeOf(types[field.type], types, opts);
|
|
24
26
|
if (field.tag == "array" || field.tag === "vec") {
|
|
25
27
|
size *= field.len;
|
|
26
28
|
}
|
|
@@ -32,7 +34,7 @@ const sizeOf = defmulti((x) => x.type, {}, {
|
|
|
32
34
|
return type.__size;
|
|
33
35
|
return (type.__size = SIZEOF[type.tag]);
|
|
34
36
|
},
|
|
35
|
-
struct: (type, types) => {
|
|
37
|
+
struct: (type, types, opts) => {
|
|
36
38
|
if (type.__size)
|
|
37
39
|
return type.__size;
|
|
38
40
|
const struct = type;
|
|
@@ -40,7 +42,7 @@ const sizeOf = defmulti((x) => x.type, {}, {
|
|
|
40
42
|
for (let f of struct.fields) {
|
|
41
43
|
size = align(size, f.__align);
|
|
42
44
|
f.__offset = size;
|
|
43
|
-
size += sizeOf(f, types);
|
|
45
|
+
size += sizeOf(f, types, opts);
|
|
44
46
|
}
|
|
45
47
|
return (type.__size = align(size, type.__align));
|
|
46
48
|
},
|
|
@@ -51,7 +53,9 @@ const alignOf = defmulti((x) => x.type, {}, {
|
|
|
51
53
|
return field.__align;
|
|
52
54
|
let align = isNumeric(field.type)
|
|
53
55
|
? SIZEOF[field.type]
|
|
54
|
-
:
|
|
56
|
+
: isWasmString(field.type)
|
|
57
|
+
? USIZE_SIZE
|
|
58
|
+
: alignOf(types[field.type], types);
|
|
55
59
|
if (field.tag === "vec") {
|
|
56
60
|
align *= ceilPow2(field.len);
|
|
57
61
|
}
|
|
@@ -71,13 +75,13 @@ const alignOf = defmulti((x) => x.type, {}, {
|
|
|
71
75
|
},
|
|
72
76
|
});
|
|
73
77
|
const prepareType = defmulti((x) => x.type, {}, {
|
|
74
|
-
[DEFAULT]: (x, types) => {
|
|
78
|
+
[DEFAULT]: (x, types, opts) => {
|
|
75
79
|
if (x.__align && x.__size)
|
|
76
80
|
return;
|
|
77
81
|
alignOf(x, types);
|
|
78
|
-
sizeOf(x, types);
|
|
82
|
+
sizeOf(x, types, opts);
|
|
79
83
|
},
|
|
80
|
-
struct: (x, types) => {
|
|
84
|
+
struct: (x, types, opts) => {
|
|
81
85
|
if (x.__align && x.__size)
|
|
82
86
|
return;
|
|
83
87
|
const struct = x;
|
|
@@ -87,30 +91,58 @@ const prepareType = defmulti((x) => x.type, {}, {
|
|
|
87
91
|
}
|
|
88
92
|
for (let f of struct.fields) {
|
|
89
93
|
if (types[f.type]) {
|
|
90
|
-
prepareType(types[f.type], types);
|
|
94
|
+
prepareType(types[f.type], types, opts);
|
|
91
95
|
}
|
|
92
96
|
}
|
|
93
|
-
sizeOf(struct, types);
|
|
97
|
+
sizeOf(struct, types, opts);
|
|
94
98
|
},
|
|
95
99
|
});
|
|
96
|
-
|
|
100
|
+
/**
|
|
101
|
+
* Takes a type collection and analyzes each analyzed to compute individual
|
|
102
|
+
* alignments and sizes.
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* This function is idempotent and called automatically by
|
|
106
|
+
* {@link generateTypes}. Only exported for dev/debug purposes.
|
|
107
|
+
*
|
|
108
|
+
* @param types
|
|
109
|
+
*
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
export const prepareTypes = (types, opts) => {
|
|
97
113
|
for (let id in types) {
|
|
98
|
-
prepareType(types[id], types);
|
|
114
|
+
prepareType(types[id], types, opts);
|
|
99
115
|
}
|
|
100
116
|
};
|
|
117
|
+
/**
|
|
118
|
+
* Code generator main entry point. Takes an object of {@link TopLevelType}
|
|
119
|
+
* definitions, an actual code generator implementation for a single target
|
|
120
|
+
* language and (optional) global codegen options. Returns generated source code
|
|
121
|
+
* for all given types as a single string.
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
124
|
+
* Before actual code generation the types are first analyzed to compute their
|
|
125
|
+
* alignments and sizes. This is only ever done once (idempotent), even if
|
|
126
|
+
* `generateTypes()` is called multiple times for different target langs.
|
|
127
|
+
*
|
|
128
|
+
* @param types
|
|
129
|
+
* @param codegen
|
|
130
|
+
* @param opts
|
|
131
|
+
*/
|
|
101
132
|
export const generateTypes = (types, codegen, opts = {}) => {
|
|
102
|
-
|
|
133
|
+
const $opts = { stringType: "slice", ...opts };
|
|
134
|
+
prepareTypes(types, $opts);
|
|
103
135
|
const res = [];
|
|
104
136
|
codegen.doc(`Generated by ${PKG_NAME} at ${new Date().toISOString()} - DO NOT EDIT!`, "", res, true);
|
|
105
137
|
res.push("");
|
|
106
138
|
codegen.pre && res.push(codegen.pre, "");
|
|
107
|
-
opts.pre && res.push(opts.pre, "");
|
|
139
|
+
$opts.pre && res.push($opts.pre, "");
|
|
108
140
|
for (let id in types) {
|
|
109
141
|
const type = types[id];
|
|
110
142
|
type.doc && codegen.doc(type.doc, "", res);
|
|
111
143
|
codegen[type.type](type, types, res);
|
|
112
144
|
}
|
|
113
|
-
opts.post && res.push("", opts.post);
|
|
145
|
+
$opts.post && res.push("", $opts.post);
|
|
114
146
|
codegen.post && res.push("", codegen.post);
|
|
115
147
|
return res.join("\n");
|
|
116
148
|
};
|
package/include/wasmapi.h
CHANGED
|
@@ -7,21 +7,26 @@ extern "C" {
|
|
|
7
7
|
#include <stddef.h>
|
|
8
8
|
#include <stdint.h>
|
|
9
9
|
|
|
10
|
+
// Declares an imported symbol from named import module
|
|
11
|
+
// The prefix is only used for the C side, NOT for exported name
|
|
10
12
|
#define WASM_IMPORT(MODULE, TYPE, NAME, PREFIX) \
|
|
11
13
|
extern __attribute__((import_module(MODULE), import_name(#NAME))) \
|
|
12
14
|
TYPE PREFIX##NAME
|
|
15
|
+
|
|
16
|
+
// Same as EMSCRIPTEN_KEEP_ALIVE, ensures symbol will be exported
|
|
13
17
|
#define WASM_KEEP __attribute__((used))
|
|
14
18
|
|
|
15
|
-
// Generate
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
void WASM_KEEP _wasm_free(size_t addr) {}
|
|
19
|
-
#else
|
|
19
|
+
// Generate malloc/free wrappers only if explicitly enabled by defining this
|
|
20
|
+
// symbol. If undefined some function stubs are exported.
|
|
21
|
+
#ifdef WASMAPI_MALLOC
|
|
20
22
|
#include <stdlib.h>
|
|
21
23
|
size_t WASM_KEEP _wasm_allocate(size_t numBytes) {
|
|
22
24
|
return (size_t)malloc(numBytes);
|
|
23
25
|
}
|
|
24
26
|
void WASM_KEEP _wasm_free(size_t addr) { free((void*)addr); }
|
|
27
|
+
#else
|
|
28
|
+
size_t WASM_KEEP _wasm_allocate(size_t num_bytes) { return 0; }
|
|
29
|
+
void WASM_KEEP _wasm_free(size_t addr) {}
|
|
25
30
|
#endif
|
|
26
31
|
|
|
27
32
|
WASM_IMPORT("wasmapi", void, printI8, wasm_)(int8_t x);
|
|
@@ -53,6 +58,8 @@ WASM_IMPORT("wasmapi", void, _printF64Array, wasm)(void* addr, size_t len);
|
|
|
53
58
|
WASM_IMPORT("wasmapi", void, _printStr0, wasm)(void* addr);
|
|
54
59
|
WASM_IMPORT("wasmapi", void, _printStr, wasm)(void* addr, size_t len);
|
|
55
60
|
|
|
61
|
+
WASM_IMPORT("wasmapi", void, debug, wasm_)(void);
|
|
62
|
+
|
|
56
63
|
void wasm_printPtr(void* ptr) { wasm_printU32Hex((size_t)ptr); }
|
|
57
64
|
|
|
58
65
|
#ifdef __cplusplus
|