@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/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
- export class WasmBridge {
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 the various typed WASM
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 { isPrim } from "./codegen/utils.js";
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 (!(isPrim(f.type) || coll[f.type])) {
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
  }
@@ -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
@@ -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, isPrim, prefixLines } from "./utils.js";
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
- var line = indent;
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 prim = isPrim(f.type);
87
+ const isPrim = isWasmPrim(f.type);
88
+ const isStr = isWasmString(f.type);
87
89
  if (f.tag === "ptr") {
88
- acc.push(prim
89
- ? `${I3}return mem.${f.type}[${__ptrShift(offset, f.type)}];`
90
- : `${I3}return $${f.type}.instance(${__ptr(offset)});`);
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)};`, prim
94
- ? `${I3}const addr = ${__ptrShift(offset, f.type)};
95
- ${I3}return mem.${f.type}.subarray(addr, addr + len);`
96
- : `${I3}const addr = ${__ptr(offset)};\n${__mapArray(f, I3)}`);
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
- acc.push(prim
100
- ? `${I3}const addr = ${__addrShift(offset, f.type)};
101
- ${I3}return mem.${f.type}.subarray(addr, addr + ${f.len});`
102
- : `${I3}const addr = ${__addr(offset)};\n${__mapArray(f, I3, f.len)}`);
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 (prim) {
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
+ ]);
@@ -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 isPrim: (x: string) => x is WasmPrim;
20
+ export declare const isWasmPrim: (x: string) => x is WasmPrim;
21
+ export declare const isWasmString: (x: string) => x is "string";
21
22
  /**
22
- * Splits given string into lines, prefixes each with given `prefix` and then
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 isPrim = (x) => isNumeric(x) || isBigNumeric(x);
19
+ export const isWasmPrim = (x) => isNumeric(x) || isBigNumeric(x);
20
+ export const isWasmString = (x) => x === "string";
19
21
  /**
20
- * Splits given string into lines, prefixes each with given `prefix` and then
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
@@ -1,4 +1,7 @@
1
1
  import type { ICodeGen } from "../api.js";
2
+ /**
3
+ * Zig code generator options.
4
+ */
2
5
  export interface ZigOpts {
3
6
  /**
4
7
  * If true, generates various struct & struct field analysis functions
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
- var ftype;
42
+ let ftype = f.type === "string" ? "[]const u8" : f.type;
43
43
  switch (f.tag) {
44
44
  case "array":
45
- ftype = `[${f.len}]${f.type}`;
45
+ ftype = `[${f.len}]${ftype}`;
46
46
  break;
47
47
  case "slice":
48
- ftype = `[]${f.type}`;
48
+ ftype = `[]${ftype}`;
49
49
  break;
50
50
  case "vec":
51
- ftype = `@Vector(${f.len}, ${f.type})`;
51
+ ftype = `@Vector(${f.len}, ${ftype})`;
52
52
  break;
53
53
  case "ptr":
54
- ftype = `*${f.len ? `[${f.len}]` : ""}${f.type}`;
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
- export declare const prepareTypes: (types: TypeColl) => void;
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
- : sizeOf(types[field.type], types);
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
- : alignOf(types[field.type], types);
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
- export const prepareTypes = (types) => {
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
- prepareTypes(types);
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 stubs only if explicitly disabled by defining this symbol
16
- #ifdef WASMAPI_NO_MALLOC
17
- size_t WASM_KEEP _wasm_allocate(size_t num_bytes) { return 0; }
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