@thi.ng/wasm-api 0.5.0 → 0.8.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.
@@ -0,0 +1,189 @@
1
+ import { BIGINT_ARRAY_CTORS, BIT_SHIFTS, TYPEDARRAY_CTORS, } from "@thi.ng/api/typedarray";
2
+ import { isString } from "@thi.ng/checks/is-string";
3
+ import { PKG_NAME, USIZE, USIZE_SIZE, } from "../api.js";
4
+ import { isBigNumeric, isNumeric, isWasmPrim, isWasmString, prefixLines, } from "./utils.js";
5
+ /**
6
+ * TypeScript code generator. Call with options and then pass to
7
+ * {@link generateTypes} (see its docs for further usage).
8
+ *
9
+ * @remarks
10
+ * This codegen generates interface and enum definitions for a {@link TypeColl}
11
+ * given to {@link generateTypes}. For structs it will also generate memory
12
+ * mapped wrappers with fully typed accessors.
13
+ *
14
+ * @param opts
15
+ */
16
+ export const TYPESCRIPT = (opts) => {
17
+ const { indent, stringType, uppercaseEnums } = {
18
+ indent: "\t",
19
+ stringType: "slice",
20
+ uppercaseEnums: true,
21
+ ...opts,
22
+ };
23
+ const I = indent;
24
+ const I2 = I + I;
25
+ const I3 = I2 + I;
26
+ const gen = {
27
+ pre: `import type { WasmTypeBase, WasmTypeConstructor } from "${PKG_NAME}";`,
28
+ doc: (doc, indent, acc) => {
29
+ if (doc.indexOf("\n") !== -1) {
30
+ acc.push(indent + "/**", prefixLines(indent + " * ", doc), indent + " */");
31
+ }
32
+ else {
33
+ acc.push(`${indent}/** ${doc} */`);
34
+ }
35
+ },
36
+ enum: (type, _, acc) => {
37
+ const e = type;
38
+ acc.push(`export enum ${e.name} {`);
39
+ for (let v of e.values) {
40
+ let line = indent;
41
+ if (!isString(v)) {
42
+ v.doc && gen.doc(v.doc, indent, acc);
43
+ line += uppercaseEnums ? v.name.toUpperCase() : v.name;
44
+ if (v.value != null)
45
+ line += ` = ${v.value}`;
46
+ }
47
+ else {
48
+ line += uppercaseEnums ? v.toUpperCase() : v;
49
+ }
50
+ acc.push(line + ",");
51
+ }
52
+ acc.push("}\n");
53
+ return acc;
54
+ },
55
+ struct: (type, types, acc) => {
56
+ const struct = type;
57
+ const returnTypes = {};
58
+ // interface definition
59
+ acc.push(`export interface ${struct.name} extends WasmTypeBase {`);
60
+ for (let f of struct.fields) {
61
+ f.doc && gen.doc(f.doc, indent, acc);
62
+ let line = `${indent}${f.name}: `;
63
+ let rtype = "";
64
+ if (f.tag == "array" || f.tag == "slice" || f.tag === "vec") {
65
+ rtype = isNumeric(f.type)
66
+ ? TYPEDARRAY_CTORS[f.type].name
67
+ : isBigNumeric(f.type)
68
+ ? BIGINT_ARRAY_CTORS[f.type].name
69
+ : f.type + "[]";
70
+ }
71
+ else if (!f.tag || f.tag === "scalar" || f.tag === "ptr") {
72
+ rtype = isBigNumeric(f.type)
73
+ ? "bigint"
74
+ : isNumeric(f.type)
75
+ ? "number"
76
+ : f.type;
77
+ }
78
+ returnTypes[f.name] = rtype;
79
+ acc.push(line + rtype + ";");
80
+ }
81
+ acc.push("}\n");
82
+ // type implementation
83
+ acc.push(`export const $${struct.name}: WasmTypeConstructor<${struct.name}> = (mem) => ({`, `${I}get align() { return ${struct.__align}; },`, `${I}get size() { return ${struct.__size}; },`, `${I}instance: (base) => ({`, `${I2}get __base() { return base; },`, `${I2}get __bytes() { return mem.u8.subarray(base, base + ${struct.__size}); },`);
84
+ for (let f of struct.fields) {
85
+ const offset = f.__offset || 0;
86
+ acc.push(`${I2}get ${f.name}(): ${returnTypes[f.name]} {`);
87
+ const isPrim = isWasmPrim(f.type);
88
+ const isStr = isWasmString(f.type);
89
+ if (f.tag === "ptr") {
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
+ }
103
+ }
104
+ else if (f.tag === "slice") {
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
+ }
115
+ }
116
+ else if (f.tag === "array" || f.tag === "vec") {
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
+ }
126
+ }
127
+ else {
128
+ let setter;
129
+ if (isPrim) {
130
+ const addr = __mem(f.type, f.__offset);
131
+ acc.push(`${I3}return ${addr};`);
132
+ setter = `${addr} = x`;
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
+ }
141
+ else if (types[f.type].type === "enum") {
142
+ const tag = types[f.type].tag;
143
+ const addr = __mem(tag, f.__offset);
144
+ acc.push(`${I3}return ${addr};`);
145
+ setter = `${addr} = x`;
146
+ }
147
+ else {
148
+ acc.push(`${I3}return $${f.type}(mem).instance(${__addr(offset)});`);
149
+ setter = `mem.u8.set(x.__bytes, ${__addr(offset)})`;
150
+ }
151
+ // close getter
152
+ acc.push(`${I2}},`);
153
+ // setter
154
+ acc.push(`${I2}set ${f.name}(x: ${returnTypes[f.name]}) {`, `${I3}${setter};`);
155
+ }
156
+ // close field accessor
157
+ acc.push(`${I2}},`);
158
+ }
159
+ acc.push(`${I}})\n});\n`);
160
+ return acc;
161
+ },
162
+ };
163
+ return gen;
164
+ };
165
+ /** @internal */
166
+ const __shift = (type) => BIT_SHIFTS[type];
167
+ /** @internal */
168
+ const __addr = (offset) => (offset > 0 ? `(base + ${offset})` : "base");
169
+ /** @internal */
170
+ const __addrShift = (offset, shift) => {
171
+ const bits = __shift(shift);
172
+ return __addr(offset) + (bits ? " >>> " + bits : "");
173
+ };
174
+ /** @internal */
175
+ const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
176
+ /** @internal */
177
+ const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift);
178
+ const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`;
179
+ /** @internal */
180
+ const __mapArray = (f, indent, len = "len") => prefixLines(indent, `const inst = $${f.type}(mem);
181
+ const slice: ${f.type}[] = [];
182
+ for(let i = 0; i < ${len}; i++) slice.push(inst.instance(addr + i * ${f.__size}));
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
+ ]);
@@ -0,0 +1,30 @@
1
+ import type { BigType } from "@thi.ng/api";
2
+ import type { WasmPrim, WasmPrim32 } from "../api.js";
3
+ /**
4
+ * Returns true iff `x` is a {@link WasmPrim32}.
5
+ *
6
+ * @param x
7
+ */
8
+ export declare const isNumeric: (x: string) => x is WasmPrim32;
9
+ /**
10
+ * Returns true iff `x` is a `i64` or `u64`.
11
+ *
12
+ * @param x
13
+ */
14
+ export declare const isBigNumeric: (x: string) => x is BigType;
15
+ /**
16
+ * Returns true iff `x` is a {@link WasmPrim}.
17
+ *
18
+ * @param x
19
+ */
20
+ export declare const isWasmPrim: (x: string) => x is WasmPrim;
21
+ export declare const isWasmString: (x: string) => x is "string";
22
+ /**
23
+ * Takes an array of strings or splits given string into lines, prefixes each
24
+ * line with given `prefix` and then returns rejoined result.
25
+ *
26
+ * @param prefix
27
+ * @param str
28
+ */
29
+ export declare const prefixLines: (prefix: string, str: string | string[]) => string;
30
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { isString } from "@thi.ng/checks/is-string";
2
+ /**
3
+ * Returns true iff `x` is a {@link WasmPrim32}.
4
+ *
5
+ * @param x
6
+ */
7
+ export const isNumeric = (x) => /^(([iu](8|16|32))|(f(32|64)))$/.test(x);
8
+ /**
9
+ * Returns true iff `x` is a `i64` or `u64`.
10
+ *
11
+ * @param x
12
+ */
13
+ export const isBigNumeric = (x) => /^[iu]64$/.test(x);
14
+ /**
15
+ * Returns true iff `x` is a {@link WasmPrim}.
16
+ *
17
+ * @param x
18
+ */
19
+ export const isWasmPrim = (x) => isNumeric(x) || isBigNumeric(x);
20
+ export const isWasmString = (x) => x === "string";
21
+ /**
22
+ * Takes an array of strings or splits given string into lines, prefixes each
23
+ * line with given `prefix` and then returns rejoined result.
24
+ *
25
+ * @param prefix
26
+ * @param str
27
+ */
28
+ export const prefixLines = (prefix, str) => (isString(str) ? str.split("\n") : str)
29
+ .map((line) => prefix + line)
30
+ .join("\n");
@@ -0,0 +1,25 @@
1
+ import type { ICodeGen } from "../api.js";
2
+ /**
3
+ * Zig code generator options.
4
+ */
5
+ export interface ZigOpts {
6
+ /**
7
+ * If true, generates various struct & struct field analysis functions
8
+ * (sizes, alignment, offsets etc.).
9
+ *
10
+ * @defaultValue false
11
+ */
12
+ debug: boolean;
13
+ }
14
+ /**
15
+ * Zig code generator. Call with options and then pass to {@link generateTypes}
16
+ * (see its docs for further usage).
17
+ *
18
+ * @remarks
19
+ * This codegen generates struct and enum definitions for a {@link TypeColl}
20
+ * given to {@link generateTypes}.
21
+ *
22
+ * @param opts
23
+ */
24
+ export declare const ZIG: (opts?: Partial<ZigOpts>) => ICodeGen;
25
+ //# sourceMappingURL=zig.d.ts.map
package/codegen/zig.js ADDED
@@ -0,0 +1,75 @@
1
+ import { isString } from "@thi.ng/checks/is-string";
2
+ import { prefixLines } from "./utils.js";
3
+ /**
4
+ * Zig code generator. Call with options and then pass to {@link generateTypes}
5
+ * (see its docs for further usage).
6
+ *
7
+ * @remarks
8
+ * This codegen generates struct and enum definitions for a {@link TypeColl}
9
+ * given to {@link generateTypes}.
10
+ *
11
+ * @param opts
12
+ */
13
+ export const ZIG = (opts) => {
14
+ const { debug } = { debug: false, ...opts };
15
+ const gen = {
16
+ doc: (doc, indent, acc, topLevel = false) => {
17
+ acc.push(prefixLines(topLevel ? "//! " : indent + "/// ", doc));
18
+ },
19
+ enum: (e, _, acc) => {
20
+ acc.push(`pub const ${e.name} = enum(${e.tag}) {`);
21
+ for (let v of e.values) {
22
+ let line = ` `;
23
+ if (!isString(v)) {
24
+ v.doc && gen.doc(v.doc, " ", acc);
25
+ line += v.name;
26
+ if (v.value != null)
27
+ line += ` = ${v.value}`;
28
+ }
29
+ else {
30
+ line += v;
31
+ }
32
+ acc.push(line + ",");
33
+ }
34
+ acc.push("};\n");
35
+ },
36
+ struct: (struct, _, acc) => {
37
+ const name = struct.name;
38
+ acc.push(`pub const ${name} = struct {`);
39
+ const ftypes = {};
40
+ for (let f of struct.fields) {
41
+ f.doc && gen.doc(f.doc, " ", acc);
42
+ let ftype = f.type === "string" ? "[]const u8" : f.type;
43
+ switch (f.tag) {
44
+ case "array":
45
+ ftype = `[${f.len}]${ftype}`;
46
+ break;
47
+ case "slice":
48
+ // ftype = `[]${f.const ? "const" : ""}${ftype}`; // TODO
49
+ ftype = `[]${ftype}`;
50
+ break;
51
+ case "vec":
52
+ ftype = `@Vector(${f.len}, ${ftype})`;
53
+ break;
54
+ case "ptr":
55
+ ftype = `*${f.len ? `[${f.len}]` : ""}${ftype}`;
56
+ break;
57
+ case "scalar":
58
+ default:
59
+ }
60
+ ftypes[f.name] = ftype;
61
+ acc.push(` ${f.name}: ${ftype},`);
62
+ }
63
+ acc.push("};\n");
64
+ if (!debug)
65
+ return;
66
+ const fn = (fname, body) => `export fn ${name}_${fname}() usize { return ${body}; }`;
67
+ acc.push(fn("align", `@alignOf(${name})`), fn("size", `@sizeOf(${name})`));
68
+ for (let f of struct.fields) {
69
+ acc.push(fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`), fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`), fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`));
70
+ }
71
+ acc.push("");
72
+ },
73
+ };
74
+ return gen;
75
+ };
package/codegen.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { ICodeGen, TypeColl } from "./api.js";
2
+ /**
3
+ * Global/shared code generator options.
4
+ */
5
+ export interface CodeGenOpts {
6
+ /**
7
+ * Optional string to be injected before generated type defs (but after
8
+ * codegen's own prelude, if any)
9
+ */
10
+ pre: string;
11
+ /**
12
+ * Optional string to be injected after generated type defs (but before
13
+ * codegen's own epilogue, if any)
14
+ */
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";
23
+ }
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
+ */
52
+ export declare const generateTypes: (types: TypeColl, codegen: ICodeGen, opts?: Partial<CodeGenOpts>) => string;
53
+ //# sourceMappingURL=codegen.d.ts.map
package/codegen.js ADDED
@@ -0,0 +1,148 @@
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
+ import { compareByKey } from "@thi.ng/compare/keys";
5
+ import { compareNumDesc } from "@thi.ng/compare/numeric";
6
+ import { DEFAULT, defmulti } from "@thi.ng/defmulti/defmulti";
7
+ import { PKG_NAME, USIZE_SIZE, } from "./api.js";
8
+ import { isNumeric, isWasmString } from "./codegen/utils.js";
9
+ const sizeOf = defmulti((x) => x.type, {}, {
10
+ [DEFAULT]: (field, types, opts) => {
11
+ if (field.__size)
12
+ return field.__size;
13
+ let size = 0;
14
+ if (field.tag === "ptr") {
15
+ size = USIZE_SIZE;
16
+ }
17
+ else if (field.tag === "slice") {
18
+ size = USIZE_SIZE * 2;
19
+ }
20
+ else {
21
+ size = isNumeric(field.type)
22
+ ? SIZEOF[field.type]
23
+ : isWasmString(field.type)
24
+ ? USIZE_SIZE * (opts.stringType === "slice" ? 2 : 1)
25
+ : sizeOf(types[field.type], types, opts);
26
+ if (field.tag == "array" || field.tag === "vec") {
27
+ size *= field.len;
28
+ }
29
+ }
30
+ return (field.__size = align(size, field.__align));
31
+ },
32
+ enum: (type) => {
33
+ if (type.__size)
34
+ return type.__size;
35
+ return (type.__size = SIZEOF[type.tag]);
36
+ },
37
+ struct: (type, types, opts) => {
38
+ if (type.__size)
39
+ return type.__size;
40
+ const struct = type;
41
+ let size = 0;
42
+ for (let f of struct.fields) {
43
+ size = align(size, f.__align);
44
+ f.__offset = size;
45
+ size += sizeOf(f, types, opts);
46
+ }
47
+ return (type.__size = align(size, type.__align));
48
+ },
49
+ });
50
+ const alignOf = defmulti((x) => x.type, {}, {
51
+ [DEFAULT]: (field, types) => {
52
+ if (field.__align)
53
+ return field.__align;
54
+ let align = isNumeric(field.type)
55
+ ? SIZEOF[field.type]
56
+ : isWasmString(field.type)
57
+ ? USIZE_SIZE
58
+ : alignOf(types[field.type], types);
59
+ if (field.tag === "vec") {
60
+ align *= ceilPow2(field.len);
61
+ }
62
+ field.__align = align;
63
+ return align;
64
+ },
65
+ enum: (e) => {
66
+ return (e.__align = SIZEOF[e.tag]);
67
+ },
68
+ struct: (type, types) => {
69
+ const struct = type;
70
+ let maxAlign = 0;
71
+ for (let f of struct.fields) {
72
+ maxAlign = Math.max(maxAlign, alignOf(f, types));
73
+ }
74
+ return (type.__align = maxAlign);
75
+ },
76
+ });
77
+ const prepareType = defmulti((x) => x.type, {}, {
78
+ [DEFAULT]: (x, types, opts) => {
79
+ if (x.__align && x.__size)
80
+ return;
81
+ alignOf(x, types);
82
+ sizeOf(x, types, opts);
83
+ },
84
+ struct: (x, types, opts) => {
85
+ if (x.__align && x.__size)
86
+ return;
87
+ const struct = x;
88
+ alignOf(struct, types);
89
+ if (struct.auto) {
90
+ struct.fields.sort(compareByKey("__align", compareNumDesc));
91
+ }
92
+ for (let f of struct.fields) {
93
+ if (types[f.type]) {
94
+ prepareType(types[f.type], types, opts);
95
+ }
96
+ }
97
+ sizeOf(struct, types, opts);
98
+ },
99
+ });
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) => {
113
+ for (let id in types) {
114
+ prepareType(types[id], types, opts);
115
+ }
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
+ */
132
+ export const generateTypes = (types, codegen, opts = {}) => {
133
+ const $opts = { stringType: "slice", ...opts };
134
+ prepareTypes(types, $opts);
135
+ const res = [];
136
+ codegen.doc(`Generated by ${PKG_NAME} at ${new Date().toISOString()} - DO NOT EDIT!`, "", res, true);
137
+ res.push("");
138
+ codegen.pre && res.push(codegen.pre, "");
139
+ $opts.pre && res.push($opts.pre, "");
140
+ for (let id in types) {
141
+ const type = types[id];
142
+ type.doc && codegen.doc(type.doc, "", res);
143
+ codegen[type.type](type, types, res);
144
+ }
145
+ $opts.post && res.push("", $opts.post);
146
+ codegen.post && res.push("", codegen.post);
147
+ return res.join("\n");
148
+ };