@thi.ng/wasm-api
Advanced tools
Comparing version 0.14.0 to 0.15.0
99
api.d.ts
@@ -1,2 +0,3 @@ | ||
import type { BigType, FloatType, Fn, IDeref, ILength, IObjectOf } from "@thi.ng/api"; | ||
import type { BigType, FloatType, Fn, Fn2, IDeref, ILength, IObjectOf } from "@thi.ng/api"; | ||
import type { Pow2 } from "@thi.ng/binary"; | ||
import type { WasmBridge } from "./bridge.js"; | ||
@@ -127,4 +128,8 @@ export declare const PKG_NAME = "@thi.ng/wasm-api"; | ||
* Core API of WASM imports defined by the {@link WasmBridge}. The same | ||
* functions are declared as bindings in `/zig/core.zig`. Also see this file for | ||
* documentation of each function... | ||
* functions are declared as bindings in `/zig/wasmapi.zig`. **Also see this | ||
* file for documentation of each function...** | ||
* | ||
* @remarks | ||
* Zig API: | ||
* https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig/wasmapi.zig | ||
*/ | ||
@@ -196,3 +201,3 @@ export interface CoreAPI extends WebAssembly.ModuleImports { | ||
/** | ||
* Auto-computed offset (in bytes) in parent struct | ||
* Auto-computed offset (in bytes) in parent struct. | ||
* | ||
@@ -203,7 +208,7 @@ * @internal | ||
/** | ||
* Auto-computed alignment (in bytes) | ||
* Auto-computed alignment (in bytes) actually used. | ||
* | ||
* @internal | ||
*/ | ||
__align?: number; | ||
__align?: Pow2; | ||
} | ||
@@ -222,3 +227,3 @@ export interface TopLevelType extends TypeInfo { | ||
*/ | ||
type: "struct" | "enum"; | ||
type: "enum" | "struct" | "union"; | ||
/** | ||
@@ -236,6 +241,6 @@ * Optional object of user provided source codes to be injected into the | ||
/** | ||
* List of struct fields (might be re-ordered if {@link Struct.auto} is | ||
* Array of struct fields (might be re-ordered if {@link Struct.auto} is | ||
* enabled). | ||
*/ | ||
fields: StructField[]; | ||
fields: Field[]; | ||
/** | ||
@@ -256,4 +261,10 @@ * If true, struct fields will be re-ordered in descending order based on | ||
tag?: "extern" | "packed"; | ||
/** | ||
* Optional user supplied {@link AlignStrategy}. By default uses | ||
* {@link ALIGN_C} or {@link ALIGN_PACKED} (if using "packed" structs). | ||
*/ | ||
align?: AlignStrategy; | ||
} | ||
export interface StructField extends TypeInfo { | ||
export declare type FieldTag = "scalar" | "array" | "ptr" | "slice" | "vec"; | ||
export interface Field extends TypeInfo { | ||
/** | ||
@@ -273,3 +284,3 @@ * Field name (prefix: "__" is reserved) | ||
* - Array & vector fields are statically sized (using | ||
* {@link StructField.len}) | ||
* {@link Field.len}) | ||
* - Pointers are emitted as single-value pointers (where this distinction | ||
@@ -284,3 +295,3 @@ * exist), i.e. even if they're pointing to multiple values, there's no | ||
*/ | ||
tag?: "scalar" | "array" | "ptr" | "slice" | "vec"; | ||
tag?: FieldTag; | ||
/** | ||
@@ -308,3 +319,3 @@ * Field base type. If not a {@link WasmPrim}, `string` or `opaque`, the | ||
/** | ||
* Array or vector length (see {@link StructField.tag}) | ||
* Array or vector length (see {@link Field.tag}) | ||
*/ | ||
@@ -318,7 +329,25 @@ len?: number; | ||
/** | ||
* If defined and > 0, the field will be considered for padding purposes only and | ||
* the value provided is the number of bytes used. | ||
* If defined and > 0, the field will be considered for padding purposes | ||
* only and the value provided is the number of bytes used. All other config | ||
* for this field will be ignored! | ||
*/ | ||
pad?: number; | ||
} | ||
export interface Union extends TopLevelType { | ||
type: "union"; | ||
/** | ||
* Array of union fields. | ||
*/ | ||
fields: Field[]; | ||
/** | ||
* Optional qualifier for the kind of struct to be emitted (codegen specific | ||
* interpretation, currently only used by {@link ZIG}). | ||
*/ | ||
tag?: "extern" | "packed"; | ||
/** | ||
* Optional user supplied {@link AlignStrategy}. By default uses | ||
* {@link ALIGN_C} or {@link ALIGN_PACKED} (if using "packed" union). | ||
*/ | ||
align?: AlignStrategy; | ||
} | ||
export interface Enum extends TopLevelType { | ||
@@ -353,2 +382,16 @@ type: "enum"; | ||
} | ||
export interface AlignStrategy { | ||
/** | ||
* Returns implementation specific alignment for given struct field. | ||
*/ | ||
align: Fn<Field, Pow2>; | ||
/** | ||
* Returns possibly rounded value for given base size & alignment. | ||
*/ | ||
size: Fn2<number, Pow2, number>; | ||
/** | ||
* Returns possibly rounded value for given base offset & alignment. | ||
*/ | ||
offset: Fn2<number, Pow2, number>; | ||
} | ||
export interface CodeGenOptsBase { | ||
@@ -359,3 +402,3 @@ /** | ||
*/ | ||
pre: string; | ||
pre?: string; | ||
/** | ||
@@ -365,3 +408,3 @@ * Optional string to be injected after generated type defs (but before | ||
*/ | ||
post: string; | ||
post?: string; | ||
} | ||
@@ -373,2 +416,8 @@ /** | ||
/** | ||
* WASM target specification. | ||
* | ||
* @defaultValue {@link WASM32} | ||
*/ | ||
target: WasmTarget; | ||
/** | ||
* Identifier how strings are stored on WASM side, e.g. in Zig string | ||
@@ -426,11 +475,19 @@ * literals are slices (8 bytes), in C just plain pointers (4 bytes). | ||
struct: (type: Struct, types: TypeColl, acc: string[], opts: CodeGenOpts) => void; | ||
/** | ||
* Codegen for union types. | ||
*/ | ||
union: (type: Union, types: TypeColl, acc: string[], opts: CodeGenOpts) => void; | ||
} | ||
export interface WasmTarget { | ||
usize: "u32" | "u64"; | ||
usizeBytes: number; | ||
} | ||
/** | ||
* WASM usize type. Assuming wasm32 until wasm64 surfaces, then need an option. | ||
* WASM32 target spec | ||
*/ | ||
export declare const USIZE = "u32"; | ||
export declare const WASM32: WasmTarget; | ||
/** | ||
* Byte size of {@link USIZE}. | ||
* WASM64 target spec | ||
*/ | ||
export declare const USIZE_SIZE = 4; | ||
export declare const WASM64: WasmTarget; | ||
//# sourceMappingURL=api.d.ts.map |
14
api.js
export const PKG_NAME = "@thi.ng/wasm-api"; | ||
export const EVENT_MEMORY_CHANGED = "memory-changed"; | ||
/** | ||
* WASM usize type. Assuming wasm32 until wasm64 surfaces, then need an option. | ||
* WASM32 target spec | ||
*/ | ||
export const USIZE = "u32"; | ||
export const WASM32 = { | ||
usize: "u32", | ||
usizeBytes: 4, | ||
}; | ||
/** | ||
* Byte size of {@link USIZE}. | ||
* WASM64 target spec | ||
*/ | ||
export const USIZE_SIZE = 4; | ||
export const WASM64 = { | ||
usize: "u64", | ||
usizeBytes: 8, | ||
}; |
# Change Log | ||
- **Last updated**: 2022-10-17T12:08:09Z | ||
- **Last updated**: 2022-10-26T12:46:53Z | ||
- **Generator**: [thi.ng/monopub](https://thi.ng/monopub) | ||
@@ -12,2 +12,37 @@ | ||
## [0.15.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.15.0) (2022-10-26) | ||
#### 🚀 Features | ||
- import ManagedIndex, migrate Zig API ([d8bb3ee](https://github.com/thi-ng/umbrella/commit/d8bb3ee)) | ||
- migrate ManagedIndex from [@thi.ng/wasm-api-dom](https://github.com/thi-ng/umbrella/tree/main/packages/wasm-api-dom) | ||
- move all Zig sources from /include => /zig | ||
- update pkg | ||
- add codegen support for union types ([bbc1f98](https://github.com/thi-ng/umbrella/commit/bbc1f98)) | ||
- add Union type, update TopLevelType | ||
- update all codegens (C,TS,Zig) | ||
- update alignOf(), sizeOf() | ||
- update selectAlignment() | ||
- add/update codegen alignment logic ([9c19ad9](https://github.com/thi-ng/umbrella/commit/9c19ad9)) | ||
- add AlignmentStrategy & impls | ||
- update alignOf(), sizeOf(), prepareType() | ||
- extract DEFAULT_CODEGEN_OPTS | ||
- add Struct.align config option | ||
- add WasmTarget codegen opt & usize support ([62c049b](https://github.com/thi-ng/umbrella/commit/62c049b)) | ||
#### 🩹 Bug fixes | ||
- update CLI wrapper to allow unions ([904716c](https://github.com/thi-ng/umbrella/commit/904716c)) | ||
- fix i64/u64 handling in sizeof() ([825add3](https://github.com/thi-ng/umbrella/commit/825add3)) | ||
- fix padding in Zig packed structs ([8d70cf6](https://github.com/thi-ng/umbrella/commit/8d70cf6)) | ||
- since packed structs can't contain `[n]u8` types, | ||
generate padding as potentially multiple `uXXX` fields | ||
#### ♻️ Refactoring | ||
- rename types, use predicates ([4148e1e](https://github.com/thi-ng/umbrella/commit/4148e1e)) | ||
- rename StructField => Field | ||
- update codegens to use more predicates instead of inline checks | ||
- minor updates C & Zig codegens ([a94e1cc](https://github.com/thi-ng/umbrella/commit/a94e1cc)) | ||
## [0.14.0](https://github.com/thi-ng/umbrella/tree/@thi.ng/wasm-api@0.14.0) (2022-10-17) | ||
@@ -14,0 +49,0 @@ |
@@ -25,3 +25,7 @@ import { flag, oneOf, oneOfMulti, parse, ParseError, string, strings, usage, } from "@thi.ng/args"; | ||
}), | ||
debug: flag({ alias: "d", default: false, desc: "enable debug output" }), | ||
debug: flag({ | ||
alias: "d", | ||
default: false, | ||
desc: "enable debug output & functions", | ||
}), | ||
dryRun: flag({ | ||
@@ -75,3 +79,3 @@ default: false, | ||
invalidSpec(path); | ||
if (!(spec.type === "enum" || spec.type === "struct")) | ||
if (!["enum", "struct", "union"].includes(spec.type)) | ||
invalidSpec(path, `${spec.name} type: ${spec.type}`); | ||
@@ -78,0 +82,0 @@ if (coll[spec.name]) |
import { CodeGenOpts, ICodeGen, TypeColl } from "./api.js"; | ||
export declare const DEFAULT_CODEGEN_OPTS: CodeGenOpts; | ||
/** | ||
@@ -14,3 +15,3 @@ * Takes a type collection and analyzes each analyzed to compute individual | ||
*/ | ||
export declare const prepareTypes: (types: TypeColl, opts: CodeGenOpts) => void; | ||
export declare const prepareTypes: (types: TypeColl, opts: CodeGenOpts) => TypeColl; | ||
/** | ||
@@ -17,0 +18,0 @@ * Code generator main entry point. Takes an object of {@link TopLevelType} |
131
codegen.js
import { SIZEOF } from "@thi.ng/api/typedarray"; | ||
import { align } from "@thi.ng/binary/align"; | ||
import { ceilPow2 } from "@thi.ng/binary/pow"; | ||
import { compareByKey } from "@thi.ng/compare/keys"; | ||
@@ -8,6 +6,15 @@ import { compareNumDesc } from "@thi.ng/compare/numeric"; | ||
import { illegalArgs } from "@thi.ng/errors/illegal-arguments"; | ||
import { PKG_NAME, USIZE_SIZE, } from "./api.js"; | ||
import { isNumeric, isWasmString } from "./codegen/utils.js"; | ||
import { PKG_NAME, WASM32, } from "./api.js"; | ||
import { selectAlignment } from "./codegen/align.js"; | ||
import { isBigNumeric, isNumeric, isPointer, isPointerLike, isSlice, isStringSlice, isWasmString, } from "./codegen/utils.js"; | ||
export const DEFAULT_CODEGEN_OPTS = { | ||
debug: false, | ||
header: true, | ||
lineWidth: 80, | ||
stringType: "slice", | ||
target: WASM32, | ||
uppercaseEnums: true, | ||
}; | ||
const sizeOf = defmulti((x) => x.type, {}, { | ||
[DEFAULT]: (field, types, opts) => { | ||
[DEFAULT]: (field, types, align, opts) => { | ||
if (field.__size) | ||
@@ -20,14 +27,16 @@ return field.__size; | ||
let size = 0; | ||
if (field.tag === "ptr") { | ||
size = USIZE_SIZE; | ||
if (isPointer(field)) { | ||
size = opts.target.usizeBytes; | ||
} | ||
else if (field.tag === "slice") { | ||
size = USIZE_SIZE * 2; | ||
else if (isSlice(field)) { | ||
size = opts.target.usizeBytes * 2; | ||
} | ||
else { | ||
size = isNumeric(field.type) | ||
? SIZEOF[field.type] | ||
: isWasmString(field.type) | ||
? USIZE_SIZE * (opts.stringType === "slice" ? 2 : 1) | ||
: sizeOf(types[field.type], types, opts); | ||
size = | ||
isNumeric(field.type) || isBigNumeric(field.type) | ||
? SIZEOF[field.type] | ||
: isWasmString(field.type) | ||
? opts.target.usizeBytes * | ||
(isStringSlice(opts.stringType) ? 2 : 1) | ||
: sizeOf(types[field.type], types, align, opts); | ||
if (field.tag == "array" || field.tag === "vec") { | ||
@@ -40,3 +49,3 @@ size *= field.len; | ||
} | ||
return (field.__size = align(size, field.__align)); | ||
return (field.__size = align.size(size, field.__align)); | ||
}, | ||
@@ -48,59 +57,74 @@ enum: (type) => { | ||
}, | ||
struct: (type, types, opts) => { | ||
struct: (type, types, align, opts) => { | ||
if (type.__size) | ||
return type.__size; | ||
const struct = type; | ||
let size = 0; | ||
for (let f of struct.fields) { | ||
size = align(size, f.__align); | ||
f.__offset = size; | ||
size += sizeOf(f, types, opts); | ||
let offset = 0; | ||
for (let f of type.fields) { | ||
offset = align.offset(offset, f.__align); | ||
f.__offset = offset; | ||
offset += sizeOf(f, types, align, opts); | ||
} | ||
return (type.__size = align(size, type.__align)); | ||
return (type.__size = align.size(offset, type.__align)); | ||
}, | ||
union: (type, types, align, opts) => { | ||
if (type.__size) | ||
return type.__size; | ||
let maxSize = 0; | ||
for (let f of type.fields) { | ||
f.__offset = 0; | ||
maxSize = Math.max(maxSize, sizeOf(f, types, align, opts)); | ||
} | ||
return (type.__size = align.size(maxSize, type.__align)); | ||
}, | ||
}); | ||
const alignOf = defmulti((x) => x.type, {}, { | ||
[DEFAULT]: (field, types) => { | ||
[DEFAULT]: (field, types, align, opts) => { | ||
if (field.__align) | ||
return field.__align; | ||
if (field.type === "usize") { | ||
field.type = opts.target.usize; | ||
} | ||
if (field.pad) | ||
return (field.__align = 1); | ||
let align = isNumeric(field.type) | ||
? SIZEOF[field.type] | ||
: isWasmString(field.type) | ||
? USIZE_SIZE | ||
: alignOf(types[field.type], types); | ||
if (field.tag === "vec") { | ||
align *= ceilPow2(field.len); | ||
} | ||
field.__align = align; | ||
return align; | ||
return (field.__align = isPointerLike(field) | ||
? align.align({ type: opts.target.usize }) | ||
: isNumeric(field.type) || isBigNumeric(field.type) | ||
? align.align(field) | ||
: alignOf(types[field.type], types, selectAlignment(types[field.type]), opts)); | ||
}, | ||
enum: (type) => { | ||
enum: (type, _, align) => { | ||
const e = type; | ||
if (!e.tag) | ||
e.tag = "i32"; | ||
return (e.__align = SIZEOF[e.tag]); | ||
return (e.__align = align.align({ | ||
type: e.tag, | ||
})); | ||
}, | ||
struct: (type, types) => { | ||
const struct = type; | ||
let maxAlign = 0; | ||
for (let f of struct.fields) { | ||
maxAlign = Math.max(maxAlign, alignOf(f, types)); | ||
struct: (type, types, align, opts) => { | ||
let maxAlign = 1; | ||
for (let f of type.fields) { | ||
maxAlign = Math.max(maxAlign, alignOf(f, types, align, opts)); | ||
} | ||
return (type.__align = maxAlign); | ||
}, | ||
union: (type, types, align, opts) => { | ||
let maxAlign = 1; | ||
for (let f of type.fields) { | ||
maxAlign = Math.max(maxAlign, alignOf(f, types, align, opts)); | ||
} | ||
return (type.__align = maxAlign); | ||
}, | ||
}); | ||
const prepareType = defmulti((x) => x.type, {}, { | ||
[DEFAULT]: (x, types, opts) => { | ||
[DEFAULT]: (x, types, alignImpl, opts) => { | ||
if (x.__align && x.__size) | ||
return; | ||
alignOf(x, types); | ||
sizeOf(x, types, opts); | ||
alignOf(x, types, alignImpl, opts); | ||
sizeOf(x, types, alignImpl, opts); | ||
}, | ||
struct: (x, types, opts) => { | ||
struct: (x, types, align, opts) => { | ||
if (x.__align && x.__size) | ||
return; | ||
const struct = x; | ||
alignOf(struct, types); | ||
alignOf(struct, types, align, opts); | ||
if (struct.auto) { | ||
@@ -110,7 +134,8 @@ struct.fields.sort(compareByKey("__align", compareNumDesc)); | ||
for (let f of struct.fields) { | ||
if (types[f.type]) { | ||
prepareType(types[f.type], types, opts); | ||
const type = types[f.type]; | ||
if (type) { | ||
prepareType(type, types, selectAlignment(type), opts); | ||
} | ||
} | ||
sizeOf(struct, types, opts); | ||
sizeOf(struct, types, align, opts); | ||
}, | ||
@@ -132,4 +157,5 @@ }); | ||
for (let id in types) { | ||
prepareType(types[id], types, opts); | ||
prepareType(types[id], types, selectAlignment(types[id]), opts); | ||
} | ||
return types; | ||
}; | ||
@@ -153,6 +179,3 @@ /** | ||
const $opts = { | ||
header: true, | ||
stringType: "slice", | ||
uppercaseEnums: true, | ||
lineWidth: 80, | ||
...DEFAULT_CODEGEN_OPTS, | ||
...opts, | ||
@@ -159,0 +182,0 @@ }; |
import { isString } from "@thi.ng/checks/is-string"; | ||
import { unsupported } from "@thi.ng/errors/unsupported"; | ||
import { enumName, isPadding, isStringSlice, prefixLines, withIndentation, } from "./utils.js"; | ||
import { enumName, isPadding, isStringSlice, isWasmString, prefixLines, withIndentation, } from "./utils.js"; | ||
const PRIM_ALIASES = { | ||
@@ -71,60 +71,76 @@ i8: "int8_t", | ||
const name = typePrefix + struct.name; | ||
const res = []; | ||
res.push(`typedef struct ${name} ${name};`, `struct ${name} {`); | ||
const ftypes = {}; | ||
let padID = 0; | ||
for (let f of struct.fields) { | ||
// autolabel explicit padding fields | ||
if (isPadding(f)) { | ||
res.push(`uint8_t __pad${padID++}[${f.pad}];`); | ||
continue; | ||
} | ||
f.doc && gen.doc(f.doc, res, opts); | ||
const fconst = f.const ? "const " : ""; | ||
let ftype = f.type === "string" | ||
? isStringSlice(opts.stringType) | ||
? __slice("char", fconst) | ||
: `${f.const !== false ? "const " : ""}char*` | ||
: PRIM_ALIASES[f.type] || f.type; | ||
if (coll[ftype]) | ||
ftype = typePrefix + ftype; | ||
switch (f.tag) { | ||
case "array": | ||
case "vec": | ||
res.push(`${fconst}${ftype} ${f.name}[${f.len}];`); | ||
ftype = `${ftype}[${f.len}]`; | ||
break; | ||
case "slice": | ||
ftype = __slice(ftype, fconst); | ||
res.push(`${ftype} ${f.name};`); | ||
break; | ||
case "ptr": | ||
ftype = `${fconst}${ftype}*`; | ||
res.push(`${ftype} ${f.name};`); | ||
break; | ||
case "scalar": | ||
default: | ||
res.push(`${ftype} ${f.name};`); | ||
} | ||
ftypes[f.name] = ftype; | ||
} | ||
res.push("};"); | ||
if (opts.debug) { | ||
const fn = (fname, body) => res.push("", `size_t __attribute__((used)) ${name}_${fname}() {`, `return ${body};`, `}`); | ||
fn("align", `alignof(${name})`); | ||
fn("size", `sizeof(${name})`); | ||
for (let f of struct.fields) { | ||
if (isPadding(f)) | ||
continue; | ||
fn(f.name + "_align", `alignof(${ftypes[f.name]})`); | ||
fn(f.name + "_offset", `offsetof(${name}, ${f.name})`); | ||
fn(f.name + "_size", `sizeof(${ftypes[f.name]})`); | ||
} | ||
} | ||
res.push(""); | ||
acc.push(...withIndentation(res, INDENT, ...SCOPES)); | ||
acc.push(...withIndentation([ | ||
`typedef struct ${name} ${name};`, | ||
`struct ${name} {`, | ||
...__generateFields(gen, struct, coll, opts, typePrefix), | ||
], INDENT, ...SCOPES)); | ||
}, | ||
union: (union, coll, acc, opts) => { | ||
const name = typePrefix + union.name; | ||
acc.push(...withIndentation([ | ||
`typedef union ${name} ${name};`, | ||
`union ${name} {`, | ||
...__generateFields(gen, union, coll, opts, typePrefix), | ||
], INDENT, ...SCOPES)); | ||
}, | ||
}; | ||
return gen; | ||
}; | ||
const __generateFields = (gen, parent, coll, opts, typePrefix) => { | ||
const res = []; | ||
const ftypes = {}; | ||
const isUnion = parent.type === "union"; | ||
const name = typePrefix + parent.name; | ||
let padID = 0; | ||
for (let f of parent.fields) { | ||
// autolabel explicit padding fields | ||
if (isPadding(f)) { | ||
res.push(`uint8_t __pad${padID++}[${f.pad}];`); | ||
continue; | ||
} | ||
f.doc && gen.doc(f.doc, res, opts); | ||
const fconst = f.const ? "const " : ""; | ||
let ftype = isWasmString(f.type) | ||
? isStringSlice(opts.stringType) | ||
? __slice("char", fconst) | ||
: `${f.const !== false ? "const " : ""}char*` | ||
: PRIM_ALIASES[f.type] || f.type; | ||
if (coll[ftype]) | ||
ftype = typePrefix + ftype; | ||
switch (f.tag) { | ||
case "array": | ||
case "vec": | ||
res.push(`${fconst}${ftype} ${f.name}[${f.len}];`); | ||
ftype = `${ftype}[${f.len}]`; | ||
break; | ||
case "slice": | ||
ftype = __slice(ftype, fconst); | ||
res.push(`${ftype} ${f.name};`); | ||
break; | ||
case "ptr": | ||
ftype = `${fconst}${ftype}*`; | ||
res.push(`${ftype} ${f.name};`); | ||
break; | ||
case "scalar": | ||
default: | ||
res.push(`${ftype} ${f.name};`); | ||
} | ||
ftypes[f.name] = ftype; | ||
} | ||
res.push("};"); | ||
if (opts.debug) { | ||
const fn = (fname, body) => res.push("", `size_t __attribute__((used)) ${name}_${fname}() {`, `return ${body};`, `}`); | ||
fn("align", `alignof(${name})`); | ||
fn("size", `sizeof(${name})`); | ||
for (let f of parent.fields) { | ||
if (isPadding(f)) | ||
continue; | ||
fn(f.name + "_align", `alignof(${ftypes[f.name]})`); | ||
!isUnion && fn(f.name + "_offset", `offsetof(${name}, ${f.name})`); | ||
fn(f.name + "_size", `sizeof(${ftypes[f.name]})`); | ||
} | ||
} | ||
res.push(""); | ||
return res; | ||
}; | ||
const __slice = (type, $const) => `struct { ${$const}${type} *ptr; size_t len; }`; |
import { BIGINT_ARRAY_CTORS, BIT_SHIFTS, TYPEDARRAY_CTORS, } from "@thi.ng/api/typedarray"; | ||
import { isString } from "@thi.ng/checks/is-string"; | ||
import { PKG_NAME, USIZE, USIZE_SIZE, } from "../api.js"; | ||
import { PKG_NAME, } from "../api.js"; | ||
import { enumName, isBigNumeric, isNumeric, isPadding, isStringSlice, isWasmPrim, isWasmString, pointerFields, prefixLines, stringFields, withIndentation, } from "./utils.js"; | ||
@@ -94,3 +94,3 @@ /** | ||
`(addr) => {`, | ||
...__mapStringArray("buf", $stringImpl, f.len, isConst, true), | ||
...__mapStringArray(opts.target, "buf", $stringImpl, f.len, isConst, true), | ||
`}`, | ||
@@ -111,11 +111,11 @@ ] | ||
else if (f.tag === "slice") { | ||
lines.push(`const len = ${__ptr(offset + 4)};`); | ||
lines.push(`const len = ${__ptr(opts.target, offset + 4)};`); | ||
if (isPrim) { | ||
lines.push(`const addr = ${__ptrShift(offset, f.type)};`, `return mem.${f.type}.subarray(addr, addr + len);`); | ||
lines.push(`const addr = ${__ptrShift(opts.target, offset, f.type)};`, `return mem.${f.type}.subarray(addr, addr + len);`); | ||
} | ||
else if (isStr) { | ||
lines.push(`const addr = ${__ptr(offset)};`, ...__mapStringArray("buf", $stringImpl, "len", isConst)); | ||
lines.push(`const addr = ${__ptr(opts.target, offset)};`, ...__mapStringArray(opts.target, "buf", $stringImpl, "len", isConst)); | ||
} | ||
else { | ||
lines.push(`const addr = ${__ptr(offset)};`, ...__mapArray(f)); | ||
lines.push(`const addr = ${__ptr(opts.target, offset)};`, ...__mapArray(f)); | ||
} | ||
@@ -128,3 +128,3 @@ } | ||
else if (isStr) { | ||
lines.push(`if ($${f.name}) return $${f.name};`, `const addr = ${__addr(offset)};`, ...__mapStringArray(f.name, $stringImpl, f.len, isConst)); | ||
lines.push(`if ($${f.name}) return $${f.name};`, `const addr = ${__addr(offset)};`, ...__mapStringArray(opts.target, f.name, $stringImpl, f.len, isConst)); | ||
} | ||
@@ -168,2 +168,5 @@ else { | ||
}, | ||
union: (type, coll, acc, opts) => { | ||
gen.struct(type, coll, acc, opts); | ||
}, | ||
}; | ||
@@ -208,5 +211,5 @@ return gen; | ||
/** @internal */ | ||
const __ptr = (offset) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`; | ||
const __ptr = (target, offset) => `mem.${target.usize}[${__addrShift(offset, target.usize)}]`; | ||
/** @internal */ | ||
const __ptrShift = (offset, shift) => __ptr(offset) + " >>> " + __shift(shift); | ||
const __ptrShift = (target, offset, shift) => __ptr(target, offset) + " >>> " + __shift(shift); | ||
const __mem = (type, offset) => `mem.${type}[${__addrShift(offset, type)}]`; | ||
@@ -221,6 +224,6 @@ /** @internal */ | ||
/** @internal */ | ||
const __mapStringArray = (name, type, len, isConst, isLocal = false) => [ | ||
const __mapStringArray = (target, name, type, len, isConst, isLocal = false) => [ | ||
isLocal ? `const $${name}: ${type}[] = [];` : `$${name} = [];`, | ||
`for(let i = 0; i < ${len}; i++) $${name}.push(new ${type}(mem, addr + i * ${USIZE_SIZE * (type === "WasmStringSlice" ? 2 : 1)}, ${isConst}));`, | ||
`for(let i = 0; i < ${len}; i++) $${name}.push(new ${type}(mem, addr + i * ${target.usizeBytes * (type === "WasmStringSlice" ? 2 : 1)}, ${isConst}));`, | ||
`return $${name};`, | ||
]; |
import type { BigType } from "@thi.ng/api"; | ||
import type { CodeGenOpts, StructField, WasmPrim, WasmPrim32 } from "../api.js"; | ||
import type { CodeGenOpts, Field, WasmPrim, WasmPrim32 } from "../api.js"; | ||
/** | ||
@@ -22,4 +22,12 @@ * Returns true iff `x` is a {@link WasmPrim32}. | ||
export declare const isWasmString: (x: string) => x is "string"; | ||
export declare const isPadding: (f: StructField) => boolean; | ||
export declare const isPadding: (f: Field) => boolean; | ||
export declare const isPointer: (f: Field) => boolean; | ||
export declare const isSlice: (f: Field) => boolean; | ||
/** | ||
* Returns true iff the struct field is a pointer, slice or "string" type | ||
* | ||
* @param f | ||
*/ | ||
export declare const isPointerLike: (f: Field) => boolean; | ||
/** | ||
* Takes an array of strings or splits given string into lines, word wraps and | ||
@@ -47,3 +55,3 @@ * then prefixes each line with given `width` and `prefix`. Returns array of new | ||
*/ | ||
export declare const pointerFields: (fields: StructField[]) => StructField[]; | ||
export declare const pointerFields: (fields: Field[]) => Field[]; | ||
/** | ||
@@ -56,3 +64,3 @@ * Returns filtered array of struct fields of only "string" fields. | ||
*/ | ||
export declare const stringFields: (fields: StructField[]) => StructField[]; | ||
export declare const stringFields: (fields: Field[]) => Field[]; | ||
/** | ||
@@ -59,0 +67,0 @@ * Returns enum identifier formatted according to given opts. |
@@ -23,3 +23,11 @@ import { isString } from "@thi.ng/checks/is-string"; | ||
export const isPadding = (f) => f.pad != null && f.pad > 0; | ||
export const isPointer = (f) => f.tag === "ptr"; | ||
export const isSlice = (f) => f.tag === "slice"; | ||
/** | ||
* Returns true iff the struct field is a pointer, slice or "string" type | ||
* | ||
* @param f | ||
*/ | ||
export const isPointerLike = (f) => isPointer(f) || isSlice(f) || isWasmString(f.type); | ||
/** | ||
* Takes an array of strings or splits given string into lines, word wraps and | ||
@@ -26,0 +34,0 @@ * then prefixes each line with given `width` and `prefix`. Returns array of new |
@@ -5,3 +5,3 @@ import { isNumber } from "@thi.ng/checks/is-number"; | ||
import { split } from "@thi.ng/strings/split"; | ||
import { enumName, isPadding, isStringSlice, prefixLines, withIndentation, } from "./utils.js"; | ||
import { enumName, isPadding, isStringSlice, isWasmString, prefixLines, withIndentation, } from "./utils.js"; | ||
/** | ||
@@ -49,73 +49,97 @@ * Zig code generator. Call with options and then pass to {@link generateTypes} | ||
struct: (struct, _, acc, opts) => { | ||
const name = struct.name; | ||
const res = []; | ||
res.push(`pub const ${name} = ${struct.tag ? struct.tag + " " : ""}struct {`); | ||
const ftypes = {}; | ||
let padID = 0; | ||
for (let f of struct.fields) { | ||
// autolabel explicit padding fields | ||
if (isPadding(f)) { | ||
res.push(`__pad${padID++}: [${f.pad}]u8,`); | ||
continue; | ||
} | ||
f.doc && gen.doc(f.doc, res, opts); | ||
let ftype = f.type === "string" | ||
? isStringSlice(opts.stringType) | ||
? f.const !== false | ||
? "[]const u8" | ||
: "[]u8" | ||
: f.const !== false | ||
? "[*:0]const u8" | ||
: "[*:0]u8" | ||
: f.type; | ||
let defaultVal = ""; | ||
switch (f.tag) { | ||
case "array": | ||
ftype = | ||
f.sentinel !== undefined | ||
? `[${f.len}:${f.sentinel}]${ftype}` | ||
: `[${f.len}]${ftype}`; | ||
break; | ||
case "slice": | ||
ftype = `[${f.sentinel !== undefined ? ":" + f.sentinel : ""}]${f.const ? "const " : ""}${ftype}`; | ||
break; | ||
case "vec": | ||
ftype = `@Vector(${f.len}, ${ftype})`; | ||
break; | ||
case "ptr": | ||
ftype = `*${f.const ? "const " : ""}${f.len ? `[${f.len}]` : ""}${ftype}`; | ||
break; | ||
case "scalar": | ||
default: | ||
if (f.default != undefined) { | ||
if (!(isString(f.default) || isNumber(f.default))) { | ||
unsupported(`wrong default value for ${name}.${f.name} (${f.default})`); | ||
} | ||
defaultVal = ` = ${JSON.stringify(f.default)}`; | ||
} | ||
} | ||
ftypes[f.name] = ftype; | ||
res.push(`${f.name}: ${ftype}${defaultVal},`); | ||
} | ||
if (struct.body?.zig) { | ||
res.push("", ...split(struct.body.zig), ""); | ||
} | ||
res.push("};"); | ||
if (opts.debug) { | ||
const fn = (fname, body) => res.push("", `export fn ${name}_${fname}() usize {`, `return ${body};`, `}`); | ||
fn("align", `@alignOf(${name})`); | ||
fn("size", `@sizeOf(${name})`); | ||
for (let f of struct.fields) { | ||
if (isPadding(f)) | ||
continue; | ||
fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`); | ||
fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`); | ||
fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`); | ||
} | ||
} | ||
res.push(""); | ||
acc.push(...withIndentation(res, INDENT, ...SCOPES)); | ||
acc.push(...withIndentation([ | ||
`pub const ${struct.name} = ${struct.tag ? struct.tag + " " : ""}struct {`, | ||
...__generateFields(gen, struct, opts), | ||
], INDENT, ...SCOPES)); | ||
}, | ||
union: (union, _, acc, opts) => { | ||
acc.push(...withIndentation([ | ||
`pub const ${union.name} = ${union.tag ? union.tag + " " : ""}union {`, | ||
...__generateFields(gen, union, opts), | ||
], INDENT, ...SCOPES)); | ||
}, | ||
}; | ||
return gen; | ||
}; | ||
const __generateFields = (gen, parent, opts) => { | ||
const res = []; | ||
const ftypes = {}; | ||
const isUnion = parent.type === "union"; | ||
const name = parent.name; | ||
let padID = 0; | ||
for (let f of parent.fields) { | ||
// autolabel explicit padding fields | ||
if (isPadding(f)) { | ||
parent.tag === "packed" | ||
? __packedPadding(padID, f.pad, res) | ||
: res.push(`__pad${padID}: [${f.pad}]u8,`); | ||
padID++; | ||
continue; | ||
} | ||
f.doc && gen.doc(f.doc, res, opts); | ||
let ftype = isWasmString(f.type) | ||
? isStringSlice(opts.stringType) | ||
? f.const !== false | ||
? "[]const u8" | ||
: "[]u8" | ||
: f.const !== false | ||
? "[*:0]const u8" | ||
: "[*:0]u8" | ||
: f.type; | ||
let defaultVal = ""; | ||
switch (f.tag) { | ||
case "array": | ||
ftype = | ||
f.sentinel !== undefined | ||
? `[${f.len}:${f.sentinel}]${ftype}` | ||
: `[${f.len}]${ftype}`; | ||
break; | ||
case "slice": | ||
ftype = `[${f.sentinel !== undefined ? ":" + f.sentinel : ""}]${f.const ? "const " : ""}${ftype}`; | ||
break; | ||
case "vec": | ||
ftype = `@Vector(${f.len}, ${ftype})`; | ||
break; | ||
case "ptr": | ||
ftype = `*${f.const ? "const " : ""}${f.len ? `[${f.len}]` : ""}${ftype}`; | ||
break; | ||
case "scalar": | ||
default: | ||
if (f.default != undefined) { | ||
if (!(isString(f.default) || isNumber(f.default))) { | ||
unsupported(`wrong default value for ${name}.${f.name} (${f.default})`); | ||
} | ||
defaultVal = ` = ${JSON.stringify(f.default)}`; | ||
} | ||
} | ||
ftypes[f.name] = ftype; | ||
res.push(`${f.name}: ${ftype}${defaultVal},`); | ||
} | ||
if (parent.body?.zig) { | ||
res.push("", ...split(parent.body.zig), ""); | ||
} | ||
res.push("};"); | ||
if (opts.debug) { | ||
const fn = (fname, body) => res.push("", `export fn ${name}_${fname}() usize {`, `return ${body};`, `}`); | ||
fn("align", `@alignOf(${name})`); | ||
fn("size", `@sizeOf(${name})`); | ||
for (let f of parent.fields) { | ||
if (isPadding(f)) | ||
continue; | ||
fn(f.name + "_align", `@alignOf(${ftypes[f.name]})`); | ||
!isUnion && | ||
fn(f.name + "_offset", `@offsetOf(${name}, "${f.name}")`); | ||
fn(f.name + "_size", `@sizeOf(${ftypes[f.name]})`); | ||
} | ||
} | ||
res.push(""); | ||
return res; | ||
}; | ||
const __packedPadding = (id, n, res) => { | ||
let i = 0; | ||
n <<= 3; | ||
for (; n >= 128; n -= 128, i++) { | ||
res.push(`__pad${id}_${i}: u128,`); | ||
} | ||
res.push(`__pad${id}_${i}: u${n},`); | ||
}; |
{ | ||
"name": "@thi.ng/wasm-api", | ||
"version": "0.14.0", | ||
"version": "0.15.0", | ||
"description": "Generic, modular, extensible API bridge, polyglot glue code and bindings code generators for hybrid JS & WebAssembly projects", | ||
@@ -36,17 +36,17 @@ "type": "module", | ||
"test": "testament test", | ||
"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" | ||
"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" | ||
}, | ||
"dependencies": { | ||
"@thi.ng/api": "^8.4.3", | ||
"@thi.ng/args": "^2.2.5", | ||
"@thi.ng/binary": "^3.3.6", | ||
"@thi.ng/checks": "^3.3.0", | ||
"@thi.ng/args": "^2.2.6", | ||
"@thi.ng/binary": "^3.3.7", | ||
"@thi.ng/checks": "^3.3.1", | ||
"@thi.ng/compare": "^2.1.13", | ||
"@thi.ng/defmulti": "^2.1.18", | ||
"@thi.ng/errors": "^2.2.2", | ||
"@thi.ng/file-io": "^0.3.15", | ||
"@thi.ng/file-io": "^0.3.16", | ||
"@thi.ng/hex": "^2.2.1", | ||
"@thi.ng/idgen": "^2.1.15", | ||
"@thi.ng/logger": "^1.4.1", | ||
"@thi.ng/paths": "^5.1.18", | ||
"@thi.ng/paths": "^5.1.19", | ||
"@thi.ng/strings": "^3.3.14" | ||
@@ -100,3 +100,4 @@ }, | ||
"codegen", | ||
"include" | ||
"include", | ||
"zig" | ||
], | ||
@@ -113,2 +114,5 @@ "exports": { | ||
}, | ||
"./codegen/align": { | ||
"default": "./codegen/align.js" | ||
}, | ||
"./codegen/c11": { | ||
@@ -143,3 +147,3 @@ "default": "./codegen/c11.js" | ||
}, | ||
"gitHead": "612b1b0bdf442473f04c2edac3012975a6ca28bb\n" | ||
"gitHead": "cc61bc0a890288bea00b8df81ffd73bc4851ffd3\n" | ||
} |
281
README.md
@@ -48,12 +48,15 @@ <!-- This file is generated - DO NOT EDIT! --> | ||
WebGL, WebGPU, WebAudio etc. is being actively worked on. | ||
3. [Include files for C11/C++ and | ||
Zig](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api/include), | ||
defining WASM imports of the extern JS [core | ||
3. Include files for | ||
[C11/C++](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api/include) | ||
and | ||
[Zig](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api/zig), | ||
defining glue code for the JS [core | ||
API](https://docs.thi.ng/umbrella/wasm-api/interfaces/CoreAPI.html) defined | ||
by this package | ||
4. Extensible shared datatype code generators for (currently) C11, TypeScript & | ||
[Zig](https://ziglang.org). The latter also generates fully type checked | ||
memory-mapped (zero-copy) accessors of WASM-side data. In general, all | ||
languages with a WebAssembly target are supported, however currently only | ||
bindings for these mentioned langs are included. | ||
4. Extensible shared datatype code generators for (currently) C11, Zig & | ||
TypeScript. The latter also generates fully type checked memory-mapped | ||
(zero-copy) accessors of WASM-side data. In general, all languages with a | ||
WebAssembly target are supported, however currently only bindings for these | ||
mentioned langs are included. Other languages require custom bindings, e.g. | ||
based on the flexible primitives provided here. | ||
5. [CLI frontend/utility](#cli-generator) to invoke the code generator(s) | ||
@@ -65,4 +68,4 @@ | ||
bilateral design & exchange of data structures shared between the WASM & JS host | ||
env. Currently, code generators for TypeScript & Zig are supplied (more are | ||
planned). A CLI wrapper is available too. See the | ||
env. Currently, code generators for TypeScript, Zig and C11 are supplied. A CLI | ||
wrapper is available too. See the | ||
[@thi.ng/wasm-api-dom](https://github.com/thi-ng/umbrella/tree/develop/packages/wasm-api-dom/) | ||
@@ -83,3 +86,3 @@ support package for a more thorough realworld example... | ||
██ █ │ | ||
█ █ █ █ █ █ █ █ │ @thi.ng/wasm-api 0.12.0 | ||
█ █ █ █ █ █ █ █ │ @thi.ng/wasm-api 0.15.0 | ||
█ █ █ █ █ █ █ █ █ │ Multi-language data bindings code generator | ||
@@ -94,3 +97,3 @@ █ │ | ||
-d, --debug enable debug output | ||
-d, --debug enable debug output & functions | ||
--dry-run enable dry run (don't overwrite files) | ||
@@ -109,4 +112,4 @@ | ||
Order is important, since the output file paths must be given in the same order | ||
as the target languages. It's recommended to be more explicit with this. An | ||
example invocation looks like: | ||
as the target languages. It's recommended to be explicit with this. An example | ||
invocation looks like: | ||
@@ -153,9 +156,10 @@ ```bash | ||
Currently, the code generator supports structs and enums. See API docs for | ||
Currently, the code generator supports enums, structs and unions. See API docs for | ||
further details: | ||
- [`Enum`](https://docs.thi.ng/umbrella/wasm-api/interfaces/Enum.html) | ||
- [`EnumValue`](https://docs.thi.ng/umbrella/wasm-api/interfaces/EnumValue.html) | ||
- [`EnumValue`](https://docs.thi.ng/umbrella/wasm-api/interfaces/EnumValue.html) (individual enum value spec) | ||
- [`Field`](https://docs.thi.ng/umbrella/wasm-api/interfaces/Field.html) (individual spec for values contained in structs/unions) | ||
- [`Struct`](https://docs.thi.ng/umbrella/wasm-api/interfaces/Struct.html) | ||
- [`StructField`](https://docs.thi.ng/umbrella/wasm-api/interfaces/StructField.html) | ||
- [`Union`](https://docs.thi.ng/umbrella/wasm-api/interfaces/Union.html) | ||
- [`TopLevelType`](https://docs.thi.ng/umbrella/wasm-api/interfaces/TopLevelType.html) | ||
@@ -165,4 +169,4 @@ | ||
Below is an example file with JSON type definitions and the resulting source | ||
codes: | ||
The following example defines 1x enum, 2x structs and 1x union. Shown here are | ||
the JSON type definitions and the resulting source codes: | ||
@@ -173,31 +177,39 @@ **⬇︎ CLICK TO EXPAND EACH CODE BLOCK ⬇︎** | ||
```json | ||
```json tangle:export/readme-types.json | ||
[ | ||
{ | ||
"name": "Foo", | ||
"name": "EventType", | ||
"type": "enum", | ||
"tag": "u8", | ||
"values": [ | ||
"unknown", | ||
{ "name": "mouse", "value": 16 }, | ||
{ "name": "key", "value": 32 } | ||
] | ||
}, | ||
{ | ||
"name": "MouseEvent", | ||
"type": "struct", | ||
"doc": "Example struct", | ||
"auto": true, | ||
"fields": [ | ||
{ "name": "id", "type": "u8", "doc": "Unique ID" }, | ||
{ "name": "bars", "type": "Bar", "tag": "array", "len": 3 }, | ||
{ "name": "color", "type": "f32", "tag": "vec", "len": 4 } | ||
{ "name": "type", "type": "EventType" }, | ||
{ "name": "pos", "type": "u16", "tag": "vec", "len": 2 } | ||
] | ||
}, | ||
{ | ||
"name": "Bar", | ||
"name": "KeyEvent", | ||
"type": "struct", | ||
"doc": "Example struct", | ||
"fields": [ | ||
{ "name": "kind", "type": "Kind" }, | ||
{ "name": "flags", "type": "u32" } | ||
{ "name": "type", "type": "EventType" }, | ||
{ "name": "key", "type": "string" }, | ||
{ "name": "modifiers", "type": "u8", "doc": "Bitmask of modifier keys" } | ||
] | ||
}, | ||
{ | ||
"name": "Kind", | ||
"type": "enum", | ||
"tag": "u16", | ||
"values": [ | ||
"unknown", | ||
{ "name": "good", "value": 100 }, | ||
{ "name": "best", "value": 1000 } | ||
"name": "Event", | ||
"type": "union", | ||
"fields": [ | ||
{ "name": "mouse", "type": "MouseEvent" }, | ||
{ "name": "key", "type": "KeyEvent" } | ||
] | ||
@@ -213,3 +225,3 @@ } | ||
/** | ||
* Generated by @thi.ng/wasm-api at 2022-10-03T15:32:12.339Z - DO NOT EDIT! | ||
* Generated by @thi.ng/wasm-api at 2022-10-26T08:36:16.825Z - DO NOT EDIT! | ||
*/ | ||
@@ -220,22 +232,66 @@ | ||
export enum EventType { | ||
UNKNOWN, | ||
MOUSE = 16, | ||
KEY = 32, | ||
} | ||
/** | ||
* Example struct | ||
*/ | ||
export interface Foo extends WasmTypeBase { | ||
color: Float32Array; | ||
bars: Bar[]; | ||
export interface MouseEvent extends WasmTypeBase { | ||
type: EventType; | ||
pos: Uint16Array; | ||
} | ||
export const $MouseEvent: WasmTypeConstructor<MouseEvent> = (mem) => ({ | ||
get align() { | ||
return 4; | ||
}, | ||
get size() { | ||
return 8; | ||
}, | ||
instance: (base) => { | ||
return { | ||
get __base() { | ||
return base; | ||
}, | ||
get __bytes() { | ||
return mem.u8.subarray(base, base + 8); | ||
}, | ||
get type(): EventType { | ||
return mem.u8[base]; | ||
}, | ||
set type(x: EventType) { | ||
mem.u8[base] = x; | ||
}, | ||
get pos(): Uint16Array { | ||
const addr = (base + 4) >>> 1; | ||
return mem.u16.subarray(addr, addr + 2); | ||
}, | ||
}; | ||
} | ||
}); | ||
/** | ||
* Example struct | ||
*/ | ||
export interface KeyEvent extends WasmTypeBase { | ||
type: EventType; | ||
key: WasmStringSlice; | ||
/** | ||
* Unique ID | ||
* Bitmask of modifier keys | ||
*/ | ||
id: number; | ||
modifiers: number; | ||
} | ||
export const $Foo: WasmTypeConstructor<Foo> = (mem) => ({ | ||
export const $KeyEvent: WasmTypeConstructor<KeyEvent> = (mem) => ({ | ||
get align() { | ||
return 16; | ||
return 4; | ||
}, | ||
get size() { | ||
return 48; | ||
return 16; | ||
}, | ||
instance: (base) => { | ||
let $key: WasmStringSlice | null = null; | ||
return { | ||
@@ -246,21 +302,19 @@ get __base() { | ||
get __bytes() { | ||
return mem.u8.subarray(base, base + 48); | ||
return mem.u8.subarray(base, base + 16); | ||
}, | ||
get color(): Float32Array { | ||
const addr = base >>> 2; | ||
return mem.f32.subarray(addr, addr + 4); | ||
get type(): EventType { | ||
return mem.u8[base]; | ||
}, | ||
get bars(): Bar[] { | ||
const addr = (base + 16); | ||
const inst = $Bar(mem); | ||
const slice: Bar[] = []; | ||
for(let i = 0; i < 3; i++) slice.push(inst.instance(addr + i * 24)); | ||
return slice; | ||
set type(x: EventType) { | ||
mem.u8[base] = x; | ||
}, | ||
get id(): number { | ||
return mem.u8[(base + 40)]; | ||
get key(): WasmStringSlice { | ||
return $key || ($key = new WasmStringSlice(mem, (base + 4), true)); | ||
}, | ||
set id(x: number) { | ||
mem.u8[(base + 40)] = x; | ||
get modifiers(): number { | ||
return mem.u8[(base + 12)]; | ||
}, | ||
set modifiers(x: number) { | ||
mem.u8[(base + 12)] = x; | ||
}, | ||
}; | ||
@@ -270,8 +324,8 @@ } | ||
export interface Bar extends WasmTypeBase { | ||
kind: Kind; | ||
flags: number; | ||
export interface Event extends WasmTypeBase { | ||
mouse: MouseEvent; | ||
key: KeyEvent; | ||
} | ||
export const $Bar: WasmTypeConstructor<Bar> = (mem) => ({ | ||
export const $Event: WasmTypeConstructor<Event> = (mem) => ({ | ||
get align() { | ||
@@ -281,3 +335,3 @@ return 4; | ||
get size() { | ||
return 8; | ||
return 16; | ||
}, | ||
@@ -290,15 +344,15 @@ instance: (base) => { | ||
get __bytes() { | ||
return mem.u8.subarray(base, base + 8); | ||
return mem.u8.subarray(base, base + 16); | ||
}, | ||
get kind(): Kind { | ||
return mem.u16[base >>> 1]; | ||
get mouse(): MouseEvent { | ||
return $MouseEvent(mem).instance(base); | ||
}, | ||
set kind(x: Kind) { | ||
mem.u16[base >>> 1] = x; | ||
set mouse(x: MouseEvent) { | ||
mem.u8.set(x.__bytes, base); | ||
}, | ||
get flags(): number { | ||
return mem.u32[(base + 4) >>> 2]; | ||
get key(): KeyEvent { | ||
return $KeyEvent(mem).instance(base); | ||
}, | ||
set flags(x: number) { | ||
mem.u32[(base + 4) >>> 2] = x; | ||
set key(x: KeyEvent) { | ||
mem.u8.set(x.__bytes, base); | ||
}, | ||
@@ -308,8 +362,2 @@ }; | ||
}); | ||
export enum Kind { | ||
UNKNOWN, | ||
GOOD = 100, | ||
BEST = 1000, | ||
} | ||
``` | ||
@@ -321,23 +369,29 @@ </details> | ||
```zig | ||
//! Generated by @thi.ng/wasm-api at 2022-10-03T15:32:12.341Z - DO NOT EDIT! | ||
//! Generated by @thi.ng/wasm-api at 2022-10-26T08:36:16.827Z - DO NOT EDIT! | ||
const std = @import("std"); | ||
pub const EventType = enum(u8) { | ||
UNKNOWN, | ||
MOUSE = 16, | ||
KEY = 32, | ||
}; | ||
/// Example struct | ||
pub const Foo = struct { | ||
color: @Vector(4, f32), | ||
bars: [3]Bar, | ||
/// Unique ID | ||
id: u8, | ||
pub const MouseEvent = struct { | ||
type: EventType, | ||
pos: @Vector(2, u16), | ||
}; | ||
pub const Bar = struct { | ||
kind: Kind, | ||
flags: u32, | ||
/// Example struct | ||
pub const KeyEvent = struct { | ||
type: EventType, | ||
key: []const u8, | ||
/// Bitmask of modifier keys | ||
modifiers: u8, | ||
}; | ||
pub const Kind = enum(u16) { | ||
UNKNOWN, | ||
GOOD = 100, | ||
BEST = 1000, | ||
pub const Event = union { | ||
mouse: MouseEvent, | ||
key: KeyEvent, | ||
}; | ||
@@ -347,3 +401,3 @@ ``` | ||
On the TypeScript/JS side, the memory-mapped wrappers (e.g. `$Foo` and `$Bar`) | ||
On the TypeScript/JS side, the memory-mapped wrappers (e.g. `$Event`) | ||
can be used in combination with the `WasmBridge` to obtain fully typed views | ||
@@ -355,3 +409,3 @@ (according to the generated types) of the underlying WASM memory. Basic usage is | ||
import { WasmBridge } from "@thi.ng/wasm-api"; | ||
import { $Foo, Kind } from "./generated.ts"; | ||
import { $Event, EventType } from "./generated.ts"; | ||
@@ -363,19 +417,20 @@ const bridge = new WasmBridge(); | ||
// Create an instance using the bridge's memory views | ||
// and mapping a `Foo` struct from given address | ||
// and mapping a `Event` union from given address | ||
// (e.g. obtained from an exported WASM function/value) | ||
const foo = $Foo(bridge).instance(0x10000); | ||
const event = $Event(bridge).instance(0x10000); | ||
// then use like normal JS object | ||
foo.color | ||
// Float32Array(4) [0.1, 0.2, 0.3, 0.4] | ||
event.mouse.pos | ||
// Uint16Array(2) [100, 200] | ||
// this even applies to arrays using other types | ||
foo.bars[2].kind = Kind.BEST; | ||
// IMPORTANT: any modifications like this are directly | ||
// applied to the underlying WASM memory... | ||
event.mouse.pos[0] = 300; | ||
event.mouse.type === EventType.MOUSE | ||
// true | ||
``` | ||
**IMPORTANT:** Struct field setters are currently only supported for single | ||
values, incl. enums, strings, structs. The latter 2 will always be copied by | ||
**IMPORTANT:** Field setters are currently only supported for single values, | ||
incl. enums, strings, structs, unions. The latter 2 will always be copied by | ||
value (mem copy). Arrays or slices of strings do not currently provide write | ||
@@ -417,5 +472,5 @@ access... | ||
- [`/include/wasmapi.zig`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/include/wasmapi.zig#L6): | ||
- [`/zig/wasmapi.zig`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig/wasmapi.zig#L64): | ||
comments about WASM-side allocator handling in Zig | ||
- [`/include/wasmapi.h`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/include/wasmapi.h#L19): | ||
- [`/include/wasmapi.h`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/include/wasmapi.h#L18): | ||
comments about WASM-side allocator handling in C/C++ | ||
@@ -513,2 +568,4 @@ - [`WasmBridge.allocate()`](https://docs.thi.ng/umbrella/wasm-api/classes/WasmBridge.html#allocate): | ||
Bindings file / lib: | ||
```zig | ||
@@ -522,5 +579,7 @@ //! custom.zig - extern definitions of custom JS API | ||
/// The bridge core API uses "wasmapi" as reserved import group name | ||
extern "custom" fn fillRandom(addr: usize, num: usize) void; | ||
extern "custom" fn fillRandom(addr: [*]f32, num: usize) void; | ||
``` | ||
Main Zig file: | ||
```zig | ||
@@ -538,3 +597,3 @@ // Import JS core API | ||
// populate foo with random numbers | ||
custom.fillRandom(@ptrToInt(&foo), foo.len); | ||
custom.fillRandom(&foo, foo.len); | ||
@@ -593,2 +652,8 @@ // print result | ||
Since v0.15.0, the supplied Zig core bindings lib also includes a | ||
[`ManagedIndex`](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api/zig/managed-index.zig) | ||
for similar dealings on the Zig side of the application. For example, in the | ||
[@thi.ng/wasm-api-dom](https://github.com/thi-ng/umbrella/blob/develop/packages/wasm-api-dom/) | ||
module this is used to manage Zig event listeners. | ||
## Status | ||
@@ -627,3 +692,3 @@ | ||
Package sizes (gzipped, pre-treeshake): ESM: 6.29 KB | ||
Package sizes (gzipped, pre-treeshake): ESM: 6.84 KB | ||
@@ -719,3 +784,3 @@ **IMPORTANT:** The package includes code generators for various languages which | ||
zig build-lib \ | ||
--pkg-begin wasmapi node_modules/@thi.ng/wasm-api/include/wasmapi.zig --pkg-end \ | ||
--pkg-begin wasmapi node_modules/@thi.ng/wasm-api/zig/wasmapi.zig --pkg-end \ | ||
-target wasm32-freestanding \ | ||
@@ -722,0 +787,0 @@ -O ReleaseSmall -dynamic --strip \ |
@@ -6,2 +6,6 @@ import type { IWasmMemoryAccess, ReadonlyWasmString } from "./api.js"; | ||
* {@link WasmStringSlice.deref} and mutated via {@link WasmStringSlice.set}. | ||
* | ||
* @remarks | ||
* Currently only supports wasm32 target, need alt. solution for 64bit (possibly | ||
* diff implementation) using bigint addresses (TODO) | ||
*/ | ||
@@ -8,0 +12,0 @@ export declare class WasmStringSlice implements ReadonlyWasmString { |
@@ -6,2 +6,6 @@ import { unsupported } from "@thi.ng/errors/unsupported"; | ||
* {@link WasmStringSlice.deref} and mutated via {@link WasmStringSlice.set}. | ||
* | ||
* @remarks | ||
* Currently only supports wasm32 target, need alt. solution for 64bit (possibly | ||
* diff implementation) using bigint addresses (TODO) | ||
*/ | ||
@@ -8,0 +12,0 @@ export class WasmStringSlice { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
181349
37
3021
902
Updated@thi.ng/args@^2.2.6
Updated@thi.ng/binary@^3.3.7
Updated@thi.ng/checks@^3.3.1
Updated@thi.ng/file-io@^0.3.16
Updated@thi.ng/paths@^5.1.19