@thi.ng/wasm-api 0.13.1 → 0.15.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/codegen/zig.js CHANGED
@@ -2,7 +2,7 @@ import { isNumber } from "@thi.ng/checks/is-number";
2
2
  import { isString } from "@thi.ng/checks/is-string";
3
3
  import { unsupported } from "@thi.ng/errors/unsupported";
4
4
  import { split } from "@thi.ng/strings/split";
5
- import { enumName, isPadding, isStringSlice, prefixLines, withIndentation, } from "./utils.js";
5
+ import { enumName, isPadding, isStringSlice, isWasmString, prefixLines, withIndentation, } from "./utils.js";
6
6
  /**
7
7
  * Zig code generator. Call with options and then pass to {@link generateTypes}
8
8
  * (see its docs for further usage).
@@ -45,75 +45,99 @@ export const ZIG = (opts = {}) => {
45
45
  acc.push(...withIndentation(lines, INDENT, ...SCOPES));
46
46
  },
47
47
  struct: (struct, _, acc, opts) => {
48
- const name = struct.name;
49
- const res = [];
50
- res.push(`pub const ${name} = ${struct.tag ? struct.tag + " " : ""}struct {`);
51
- const ftypes = {};
52
- let padID = 0;
53
- for (let f of struct.fields) {
54
- // autolabel explicit padding fields
55
- if (isPadding(f)) {
56
- res.push(`__pad${padID++}: [${f.pad}]u8,`);
57
- continue;
58
- }
59
- f.doc && gen.doc(f.doc, res, opts);
60
- let ftype = f.type === "string"
61
- ? isStringSlice(opts.stringType)
62
- ? f.const !== false
63
- ? "[]const u8"
64
- : "[]u8"
65
- : f.const !== false
66
- ? "[*:0]const u8"
67
- : "[*:0]u8"
68
- : f.type;
69
- let defaultVal = "";
70
- switch (f.tag) {
71
- case "array":
72
- ftype =
73
- f.sentinel !== undefined
74
- ? `[${f.len}:${f.sentinel}]${ftype}`
75
- : `[${f.len}]${ftype}`;
76
- break;
77
- case "slice":
78
- ftype = `[${f.sentinel !== undefined ? ":" + f.sentinel : ""}]${f.const ? "const " : ""}${ftype}`;
79
- break;
80
- case "vec":
81
- ftype = `@Vector(${f.len}, ${ftype})`;
82
- break;
83
- case "ptr":
84
- ftype = `*${f.const ? "const " : ""}${f.len ? `[${f.len}]` : ""}${ftype}`;
85
- break;
86
- case "scalar":
87
- default:
88
- if (f.default != undefined) {
89
- if (!(isString(f.default) || isNumber(f.default))) {
90
- unsupported(`wrong default value for ${name}.${f.name} (${f.default})`);
91
- }
92
- defaultVal = ` = ${JSON.stringify(f.default)}`;
93
- }
94
- }
95
- ftypes[f.name] = ftype;
96
- res.push(`${f.name}: ${ftype}${defaultVal},`);
97
- }
98
- if (struct.body?.zig) {
99
- res.push("", ...split(struct.body.zig), "");
100
- }
101
- res.push("};");
102
- if (opts.debug) {
103
- const fn = (fname, body) => res.push("", `export fn ${name}_${fname}() usize {`, `return ${body};`, `}`);
104
- fn("align", `@alignOf(${name})`);
105
- fn("size", `@sizeOf(${name})`);
106
- for (let f of struct.fields) {
107
- if (isPadding(f))
108
- continue;
109
- fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`);
110
- fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`);
111
- fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`);
112
- }
113
- }
114
- res.push("");
115
- acc.push(...withIndentation(res, INDENT, ...SCOPES));
48
+ acc.push(...withIndentation([
49
+ `pub const ${struct.name} = ${struct.tag ? struct.tag + " " : ""}struct {`,
50
+ ...__generateFields(gen, struct, opts),
51
+ ], INDENT, ...SCOPES));
52
+ },
53
+ union: (union, _, acc, opts) => {
54
+ acc.push(...withIndentation([
55
+ `pub const ${union.name} = ${union.tag ? union.tag + " " : ""}union {`,
56
+ ...__generateFields(gen, union, opts),
57
+ ], INDENT, ...SCOPES));
116
58
  },
117
59
  };
118
60
  return gen;
119
61
  };
62
+ const __generateFields = (gen, parent, opts) => {
63
+ const res = [];
64
+ const ftypes = {};
65
+ const isUnion = parent.type === "union";
66
+ const name = parent.name;
67
+ let padID = 0;
68
+ for (let f of parent.fields) {
69
+ // autolabel explicit padding fields
70
+ if (isPadding(f)) {
71
+ parent.tag === "packed"
72
+ ? __packedPadding(padID, f.pad, res)
73
+ : res.push(`__pad${padID}: [${f.pad}]u8,`);
74
+ padID++;
75
+ continue;
76
+ }
77
+ f.doc && gen.doc(f.doc, res, opts);
78
+ let ftype = isWasmString(f.type)
79
+ ? isStringSlice(opts.stringType)
80
+ ? f.const !== false
81
+ ? "[]const u8"
82
+ : "[]u8"
83
+ : f.const !== false
84
+ ? "[*:0]const u8"
85
+ : "[*:0]u8"
86
+ : f.type;
87
+ let defaultVal = "";
88
+ switch (f.tag) {
89
+ case "array":
90
+ ftype =
91
+ f.sentinel !== undefined
92
+ ? `[${f.len}:${f.sentinel}]${ftype}`
93
+ : `[${f.len}]${ftype}`;
94
+ break;
95
+ case "slice":
96
+ ftype = `[${f.sentinel !== undefined ? ":" + f.sentinel : ""}]${f.const ? "const " : ""}${ftype}`;
97
+ break;
98
+ case "vec":
99
+ ftype = `@Vector(${f.len}, ${ftype})`;
100
+ break;
101
+ case "ptr":
102
+ ftype = `*${f.const ? "const " : ""}${f.len ? `[${f.len}]` : ""}${ftype}`;
103
+ break;
104
+ case "scalar":
105
+ default:
106
+ if (f.default != undefined) {
107
+ if (!(isString(f.default) || isNumber(f.default))) {
108
+ unsupported(`wrong default value for ${name}.${f.name} (${f.default})`);
109
+ }
110
+ defaultVal = ` = ${JSON.stringify(f.default)}`;
111
+ }
112
+ }
113
+ ftypes[f.name] = ftype;
114
+ res.push(`${f.name}: ${ftype}${defaultVal},`);
115
+ }
116
+ if (parent.body?.zig) {
117
+ res.push("", ...split(parent.body.zig), "");
118
+ }
119
+ res.push("};");
120
+ if (opts.debug) {
121
+ const fn = (fname, body) => res.push("", `export fn ${name}_${fname}() usize {`, `return ${body};`, `}`);
122
+ fn("align", `@alignOf(${name})`);
123
+ fn("size", `@sizeOf(${name})`);
124
+ for (let f of parent.fields) {
125
+ if (isPadding(f))
126
+ continue;
127
+ fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`);
128
+ !isUnion &&
129
+ fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`);
130
+ fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`);
131
+ }
132
+ }
133
+ res.push("");
134
+ return res;
135
+ };
136
+ const __packedPadding = (id, n, res) => {
137
+ let i = 0;
138
+ n <<= 3;
139
+ for (; n >= 128; n -= 128, i++) {
140
+ res.push(`__pad${id}_${i}: u128,`);
141
+ }
142
+ res.push(`__pad${id}_${i}: u${n},`);
143
+ };
package/codegen.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { CodeGenOpts, ICodeGen, TypeColl } from "./api.js";
2
+ export declare const DEFAULT_CODEGEN_OPTS: CodeGenOpts;
2
3
  /**
3
4
  * Takes a type collection and analyzes each analyzed to compute individual
4
5
  * alignments and sizes.
@@ -11,7 +12,7 @@ import { CodeGenOpts, ICodeGen, TypeColl } from "./api.js";
11
12
  *
12
13
  * @internal
13
14
  */
14
- export declare const prepareTypes: (types: TypeColl, opts: CodeGenOpts) => void;
15
+ export declare const prepareTypes: (types: TypeColl, opts: CodeGenOpts) => TypeColl;
15
16
  /**
16
17
  * Code generator main entry point. Takes an object of {@link TopLevelType}
17
18
  * definitions, an actual code generator implementation for a single target
package/codegen.js CHANGED
@@ -1,14 +1,21 @@
1
1
  import { SIZEOF } from "@thi.ng/api/typedarray";
2
- import { align } from "@thi.ng/binary/align";
3
- import { ceilPow2 } from "@thi.ng/binary/pow";
4
2
  import { compareByKey } from "@thi.ng/compare/keys";
5
3
  import { compareNumDesc } from "@thi.ng/compare/numeric";
6
4
  import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti";
7
5
  import { illegalArgs } from "@thi.ng/errors/illegal-arguments";
8
- import { PKG_NAME, USIZE_SIZE, } from "./api.js";
9
- import { isNumeric, isWasmString } from "./codegen/utils.js";
6
+ import { PKG_NAME, WASM32, } from "./api.js";
7
+ import { selectAlignment } from "./codegen/align.js";
8
+ import { isBigNumeric, isNumeric, isPointer, isPointerLike, isSlice, isStringSlice, isWasmString, } from "./codegen/utils.js";
9
+ export const DEFAULT_CODEGEN_OPTS = {
10
+ debug: false,
11
+ header: true,
12
+ lineWidth: 80,
13
+ stringType: "slice",
14
+ target: WASM32,
15
+ uppercaseEnums: true,
16
+ };
10
17
  const sizeOf = defmulti((x) => x.type, {}, {
11
- [DEFAULT]: (field, types, opts) => {
18
+ [DEFAULT]: (field, types, align, opts) => {
12
19
  if (field.__size)
13
20
  return field.__size;
14
21
  if (field.pad != null) {
@@ -16,18 +23,20 @@ const sizeOf = defmulti((x) => x.type, {}, {
16
23
  return (field.__size = field.pad);
17
24
  }
18
25
  let size = 0;
19
- if (field.tag === "ptr") {
20
- size = USIZE_SIZE;
26
+ if (isPointer(field)) {
27
+ size = opts.target.usizeBytes;
21
28
  }
22
- else if (field.tag === "slice") {
23
- size = USIZE_SIZE * 2;
29
+ else if (isSlice(field)) {
30
+ size = opts.target.usizeBytes * 2;
24
31
  }
25
32
  else {
26
- size = isNumeric(field.type)
27
- ? SIZEOF[field.type]
28
- : isWasmString(field.type)
29
- ? USIZE_SIZE * (opts.stringType === "slice" ? 2 : 1)
30
- : sizeOf(types[field.type], types, opts);
33
+ size =
34
+ isNumeric(field.type) || isBigNumeric(field.type)
35
+ ? SIZEOF[field.type]
36
+ : isWasmString(field.type)
37
+ ? opts.target.usizeBytes *
38
+ (isStringSlice(opts.stringType) ? 2 : 1)
39
+ : sizeOf(types[field.type], types, align, opts);
31
40
  if (field.tag == "array" || field.tag === "vec") {
32
41
  size *= field.len;
33
42
  if (field.sentinel !== undefined && field.tag === "array") {
@@ -35,79 +44,95 @@ const sizeOf = defmulti((x) => x.type, {}, {
35
44
  }
36
45
  }
37
46
  }
38
- return (field.__size = align(size, field.__align));
47
+ return (field.__size = align.size(size, field.__align));
39
48
  },
40
49
  enum: (type) => {
41
50
  if (type.__size)
42
51
  return type.__size;
43
52
  return (type.__size = SIZEOF[type.tag]);
44
53
  },
45
- struct: (type, types, opts) => {
54
+ struct: (type, types, align, opts) => {
46
55
  if (type.__size)
47
56
  return type.__size;
48
- const struct = type;
49
- let size = 0;
50
- for (let f of struct.fields) {
51
- size = align(size, f.__align);
52
- f.__offset = size;
53
- size += sizeOf(f, types, opts);
57
+ let offset = 0;
58
+ for (let f of type.fields) {
59
+ offset = align.offset(offset, f.__align);
60
+ f.__offset = offset;
61
+ offset += sizeOf(f, types, align, opts);
62
+ }
63
+ return (type.__size = align.size(offset, type.__align));
64
+ },
65
+ union: (type, types, align, opts) => {
66
+ if (type.__size)
67
+ return type.__size;
68
+ let maxSize = 0;
69
+ for (let f of type.fields) {
70
+ f.__offset = 0;
71
+ maxSize = Math.max(maxSize, sizeOf(f, types, align, opts));
54
72
  }
55
- return (type.__size = align(size, type.__align));
73
+ return (type.__size = align.size(maxSize, type.__align));
56
74
  },
57
75
  });
58
76
  const alignOf = defmulti((x) => x.type, {}, {
59
- [DEFAULT]: (field, types) => {
77
+ [DEFAULT]: (field, types, align, opts) => {
60
78
  if (field.__align)
61
79
  return field.__align;
80
+ if (field.type === "usize") {
81
+ field.type = opts.target.usize;
82
+ }
62
83
  if (field.pad)
63
84
  return (field.__align = 1);
64
- let align = isNumeric(field.type)
65
- ? SIZEOF[field.type]
66
- : isWasmString(field.type)
67
- ? USIZE_SIZE
68
- : alignOf(types[field.type], types);
69
- if (field.tag === "vec") {
70
- align *= ceilPow2(field.len);
71
- }
72
- field.__align = align;
73
- return align;
85
+ return (field.__align = isPointerLike(field)
86
+ ? align.align({ type: opts.target.usize })
87
+ : isNumeric(field.type) || isBigNumeric(field.type)
88
+ ? align.align(field)
89
+ : alignOf(types[field.type], types, selectAlignment(types[field.type]), opts));
74
90
  },
75
- enum: (type) => {
91
+ enum: (type, _, align) => {
76
92
  const e = type;
77
93
  if (!e.tag)
78
94
  e.tag = "i32";
79
- return (e.__align = SIZEOF[e.tag]);
95
+ return (e.__align = align.align({
96
+ type: e.tag,
97
+ }));
80
98
  },
81
- struct: (type, types) => {
82
- const struct = type;
83
- let maxAlign = 0;
84
- for (let f of struct.fields) {
85
- maxAlign = Math.max(maxAlign, alignOf(f, types));
99
+ struct: (type, types, align, opts) => {
100
+ let maxAlign = 1;
101
+ for (let f of type.fields) {
102
+ maxAlign = Math.max(maxAlign, alignOf(f, types, align, opts));
103
+ }
104
+ return (type.__align = maxAlign);
105
+ },
106
+ union: (type, types, align, opts) => {
107
+ let maxAlign = 1;
108
+ for (let f of type.fields) {
109
+ maxAlign = Math.max(maxAlign, alignOf(f, types, align, opts));
86
110
  }
87
111
  return (type.__align = maxAlign);
88
112
  },
89
113
  });
90
114
  const prepareType = defmulti((x) => x.type, {}, {
91
- [DEFAULT]: (x, types, opts) => {
115
+ [DEFAULT]: (x, types, alignImpl, opts) => {
92
116
  if (x.__align && x.__size)
93
117
  return;
94
- alignOf(x, types);
95
- sizeOf(x, types, opts);
118
+ alignOf(x, types, alignImpl, opts);
119
+ sizeOf(x, types, alignImpl, opts);
96
120
  },
97
- struct: (x, types, opts) => {
121
+ struct: (x, types, align, opts) => {
98
122
  if (x.__align && x.__size)
99
123
  return;
100
124
  const struct = x;
101
- alignOf(struct, types);
125
+ alignOf(struct, types, align, opts);
102
126
  if (struct.auto) {
103
127
  struct.fields.sort(compareByKey("__align", compareNumDesc));
104
128
  }
105
129
  for (let f of struct.fields) {
106
- if (types[f.type]) {
107
- prepareType(types[f.type], types, opts);
130
+ const type = types[f.type];
131
+ if (type) {
132
+ prepareType(type, types, selectAlignment(type), opts);
108
133
  }
109
134
  }
110
- sizeOf(struct, types, opts);
135
+ sizeOf(struct, types, align, opts);
111
136
  },
112
137
  });
113
138
  /**
@@ -124,8 +149,9 @@ const prepareType = defmulti((x) => x.type, {}, {
124
149
  */
125
150
  export const prepareTypes = (types, opts) => {
126
151
  for (let id in types) {
127
- prepareType(types[id], types, opts);
152
+ prepareType(types[id], types, selectAlignment(types[id]), opts);
128
153
  }
154
+ return types;
129
155
  };
130
156
  /**
131
157
  * Code generator main entry point. Takes an object of {@link TopLevelType}
@@ -144,10 +170,7 @@ export const prepareTypes = (types, opts) => {
144
170
  */
145
171
  export const generateTypes = (types, codegen, opts = {}) => {
146
172
  const $opts = {
147
- header: true,
148
- stringType: "slice",
149
- uppercaseEnums: true,
150
- lineWidth: 80,
173
+ ...DEFAULT_CODEGEN_OPTS,
151
174
  ...opts,
152
175
  };
153
176
  prepareTypes(types, $opts);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thi.ng/wasm-api",
3
- "version": "0.13.1",
3
+ "version": "0.15.0",
4
4
  "description": "Generic, modular, extensible API bridge, polyglot glue code and bindings code generators for hybrid JS & WebAssembly projects",
5
5
  "type": "module",
6
6
  "module": "./index.js",
@@ -33,26 +33,26 @@
33
33
  "doc:stats": "tools:module-stats",
34
34
  "pub": "yarn npm publish --access public",
35
35
  "test": "testament test",
36
- "test:build-zig": "zig build-lib -O ReleaseSmall -target wasm32-freestanding -dynamic --strip --pkg-begin wasmapi include/wasmapi.zig --pkg-end test/custom.zig && wasm-dis -o custom.wast custom.wasm && cp custom.wasm test"
36
+ "test:build-zig": "zig build-lib -O ReleaseSmall -target wasm32-freestanding -dynamic --strip --pkg-begin wasmapi zig/wasmapi.zig --pkg-end test/custom.zig && wasm-dis -o custom.wast custom.wasm && cp custom.wasm test"
37
37
  },
38
38
  "dependencies": {
39
39
  "@thi.ng/api": "^8.4.3",
40
- "@thi.ng/args": "^2.2.4",
41
- "@thi.ng/binary": "^3.3.6",
42
- "@thi.ng/checks": "^3.2.6",
40
+ "@thi.ng/args": "^2.2.6",
41
+ "@thi.ng/binary": "^3.3.7",
42
+ "@thi.ng/checks": "^3.3.1",
43
43
  "@thi.ng/compare": "^2.1.13",
44
- "@thi.ng/defmulti": "^2.1.17",
44
+ "@thi.ng/defmulti": "^2.1.18",
45
45
  "@thi.ng/errors": "^2.2.2",
46
- "@thi.ng/file-io": "^0.3.14",
46
+ "@thi.ng/file-io": "^0.3.16",
47
47
  "@thi.ng/hex": "^2.2.1",
48
48
  "@thi.ng/idgen": "^2.1.15",
49
- "@thi.ng/logger": "^1.4.0",
50
- "@thi.ng/paths": "^5.1.17",
49
+ "@thi.ng/logger": "^1.4.1",
50
+ "@thi.ng/paths": "^5.1.19",
51
51
  "@thi.ng/strings": "^3.3.14"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@microsoft/api-extractor": "^7.31.1",
55
- "@thi.ng/testament": "^0.3.2",
55
+ "@thi.ng/testament": "^0.3.3",
56
56
  "rimraf": "^3.0.2",
57
57
  "tools": "^0.0.1",
58
58
  "typedoc": "^0.22.17",
@@ -62,13 +62,15 @@
62
62
  "allocator",
63
63
  "api",
64
64
  "bindings",
65
+ "browser",
65
66
  "c",
66
67
  "codegen",
67
68
  "enum",
68
69
  "event",
69
- "id",
70
+ "interop",
70
71
  "logger",
71
72
  "memory",
73
+ "modular",
72
74
  "polyglot",
73
75
  "string",
74
76
  "struct",
@@ -77,7 +79,6 @@
77
79
  "utf8",
78
80
  "wasm",
79
81
  "webassembly",
80
- "wrapper",
81
82
  "ziglang"
82
83
  ],
83
84
  "publishConfig": {
@@ -94,8 +95,9 @@
94
95
  "*.js",
95
96
  "*.d.ts",
96
97
  "bin",
98
+ "codegen",
97
99
  "include",
98
- "codegen"
100
+ "zig"
99
101
  ],
100
102
  "exports": {
101
103
  ".": {
@@ -107,6 +109,9 @@
107
109
  "./bridge": {
108
110
  "default": "./bridge.js"
109
111
  },
112
+ "./codegen/align": {
113
+ "default": "./codegen/align.js"
114
+ },
110
115
  "./codegen/c11": {
111
116
  "default": "./codegen/c11.js"
112
117
  },
@@ -136,5 +141,5 @@
136
141
  "status": "alpha",
137
142
  "year": 2022
138
143
  },
139
- "gitHead": "c3f7d6598c7d7d0c61ba87150a56deac12649d7b\n"
144
+ "gitHead": "cc61bc0a890288bea00b8df81ffd73bc4851ffd3\n"
140
145
  }
package/string.d.ts CHANGED
@@ -3,6 +3,10 @@ import type { IWasmMemoryAccess, ReadonlyWasmString } from "./api.js";
3
3
  * Memory mapped string wrapper for Zig-style UTF-8 encoded byte slices (aka
4
4
  * pointer & length pair). The actual JS string can be obtained via
5
5
  * {@link WasmStringSlice.deref} and mutated via {@link WasmStringSlice.set}.
6
+ *
7
+ * @remarks
8
+ * Currently only supports wasm32 target, need alt. solution for 64bit (possibly
9
+ * diff implementation) using bigint addresses (TODO)
6
10
  */
7
11
  export declare class WasmStringSlice implements ReadonlyWasmString {
8
12
  readonly mem: IWasmMemoryAccess;
package/string.js CHANGED
@@ -3,6 +3,10 @@ import { unsupported } from "@thi.ng/errors/unsupported";
3
3
  * Memory mapped string wrapper for Zig-style UTF-8 encoded byte slices (aka
4
4
  * pointer & length pair). The actual JS string can be obtained via
5
5
  * {@link WasmStringSlice.deref} and mutated via {@link WasmStringSlice.set}.
6
+ *
7
+ * @remarks
8
+ * Currently only supports wasm32 target, need alt. solution for 64bit (possibly
9
+ * diff implementation) using bigint addresses (TODO)
6
10
  */
7
11
  export class WasmStringSlice {
8
12
  constructor(mem, base, isConst = true) {
@@ -15,12 +19,14 @@ export class WasmStringSlice {
15
19
  * Returns string start address (deref'd pointer).
16
20
  */
17
21
  get addr() {
22
+ this.mem.ensureMemory();
18
23
  return this.mem.u32[this.base >>> 2];
19
24
  }
20
25
  /**
21
26
  * Returns string length (read from memory)
22
27
  */
23
28
  get length() {
29
+ this.mem.ensureMemory();
24
30
  return this.mem.u32[(this.base + 4) >>> 2];
25
31
  }
26
32
  /**
@@ -48,6 +54,7 @@ export class WasmStringSlice {
48
54
  * @param str
49
55
  */
50
56
  set(str) {
57
+ this.mem.ensureMemory();
51
58
  if (typeof str === "string") {
52
59
  if (this.isConst)
53
60
  unsupported("can't mutate const string");
@@ -65,6 +72,7 @@ export class WasmStringSlice {
65
72
  * @param len
66
73
  */
67
74
  setSlice(addr, len) {
75
+ this.mem.ensureMemory();
68
76
  this.mem.u32[this.base >>> 2] = addr;
69
77
  this.mem.u32[(this.base + 4) >>> 2] = len;
70
78
  }
@@ -93,18 +101,20 @@ export class WasmStringPtr {
93
101
  * Returns string start address (deref'd pointer).
94
102
  */
95
103
  get addr() {
104
+ this.mem.ensureMemory();
96
105
  return this.mem.u32[this.base >>> 2];
97
106
  }
98
107
  set addr(addr) {
108
+ this.mem.ensureMemory();
99
109
  this.mem.u32[this.base >>> 2] = addr;
100
110
  }
101
111
  /**
102
112
  * Returns computed string length (scanning memory for zero sentinel)
103
113
  */
104
114
  get length() {
105
- const addr = this.addr;
106
- const idx = this.mem.u8.indexOf(0, addr);
107
- return idx >= 0 ? idx - addr : 0;
115
+ this.mem.ensureMemory();
116
+ const idx = this.mem.u8.indexOf(0, this.addr);
117
+ return idx >= 0 ? idx - this.addr : 0;
108
118
  }
109
119
  /**
110
120
  * Returns memory as JS string (aka wrapper for
@@ -137,6 +147,7 @@ export class WasmStringPtr {
137
147
  if (typeof str === "string") {
138
148
  if (this.isConst)
139
149
  unsupported("can't mutate const string");
150
+ this.mem.ensureMemory();
140
151
  this.mem.setString(str, addr, this.mem.u8.byteLength - addr, true);
141
152
  }
142
153
  else {