Skip to content

Commit

Permalink
feat(wasm-api): add WasmTarget codegen opt & usize support
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Oct 21, 2022
1 parent ff2551f commit 62c049b
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 27 deletions.
26 changes: 22 additions & 4 deletions packages/wasm-api/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,12 @@ export interface CodeGenOptsBase {
* Global/shared code generator options.
*/
export interface CodeGenOpts extends CodeGenOptsBase {
/**
* WASM target specification.
*
* @defaultValue {@link WASM32}
*/
target: WasmTarget;
/**
* Identifier how strings are stored on WASM side, e.g. in Zig string
* literals are slices (8 bytes), in C just plain pointers (4 bytes).
Expand Down Expand Up @@ -464,11 +470,23 @@ export interface ICodeGen {
) => 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 const USIZE = "u32";
export const WASM32: WasmTarget = {
usize: "u32",
usizeBytes: 4,
};

/**
* Byte size of {@link USIZE}.
* WASM64 target spec
*/
export const USIZE_SIZE = 4;
export const WASM64: WasmTarget = {
usize: "u64",
usizeBytes: 8,
};
36 changes: 23 additions & 13 deletions packages/wasm-api/src/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
StructField,
TopLevelType,
TypeColl,
USIZE_SIZE,
WASM32,
} from "./api.js";
import { isNumeric, isWasmString } from "./codegen/utils.js";

Expand All @@ -36,14 +36,15 @@ const sizeOf = defmulti<
}
let size = 0;
if (field.tag === "ptr") {
size = USIZE_SIZE;
size = opts.target.usizeBytes;
} else if (field.tag === "slice") {
size = USIZE_SIZE * 2;
size = opts.target.usizeBytes * 2;
} else {
size = isNumeric(field.type)
? SIZEOF[<Type>field.type]
: isWasmString(field.type)
? USIZE_SIZE * (opts.stringType === "slice" ? 2 : 1)
? opts.target.usizeBytes *
(opts.stringType === "slice" ? 2 : 1)
: sizeOf(types[field.type], types, opts);
if (field.tag == "array" || field.tag === "vec") {
size *= field.len!;
Expand Down Expand Up @@ -74,18 +75,26 @@ const sizeOf = defmulti<
}
);

const alignOf = defmulti<TopLevelType | StructField, TypeColl, number>(
const alignOf = defmulti<
TopLevelType | StructField,
TypeColl,
CodeGenOpts,
number
>(
(x) => x.type,
{},
{
[DEFAULT]: (field: StructField, types: TypeColl) => {
[DEFAULT]: (field: StructField, types: TypeColl, opts: CodeGenOpts) => {
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[<Type>field.type]
: isWasmString(field.type)
? USIZE_SIZE
: alignOf(types[field.type], types);
? opts.target.usizeBytes
: alignOf(types[field.type], types, opts);
if (field.tag === "vec") {
align *= ceilPow2(field.len!);
}
Expand All @@ -99,11 +108,11 @@ const alignOf = defmulti<TopLevelType | StructField, TypeColl, number>(
return (e.__align = SIZEOF[(<Enum>e).tag]);
},

struct: (type, types) => {
struct: (type, types, opts) => {
const struct = <Struct>type;
let maxAlign = 0;
for (let f of struct.fields) {
maxAlign = Math.max(maxAlign, alignOf(f, types));
maxAlign = Math.max(maxAlign, alignOf(f, types, opts));
}
return (type.__align = maxAlign);
},
Expand All @@ -116,13 +125,13 @@ const prepareType = defmulti<TopLevelType, TypeColl, CodeGenOpts, void>(
{
[DEFAULT]: (x: TopLevelType, types: TypeColl, opts: CodeGenOpts) => {
if (x.__align && x.__size) return;
alignOf(x, types);
alignOf(x, types, opts);
sizeOf(x, types, opts);
},
struct: (x, types, opts) => {
if (x.__align && x.__size) return;
const struct = <Struct>x;
alignOf(struct, types);
alignOf(struct, types, opts);
if (struct.auto) {
struct.fields.sort(
compareByKey("__align", <any>compareNumDesc)
Expand Down Expand Up @@ -178,9 +187,10 @@ export const generateTypes = (
) => {
const $opts = <CodeGenOpts>{
header: true,
lineWidth: 80,
stringType: "slice",
target: WASM32,
uppercaseEnums: true,
lineWidth: 80,
...opts,
};
prepareTypes(types, $opts);
Expand Down
30 changes: 20 additions & 10 deletions packages/wasm-api/src/codegen/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ import {
ICodeGen,
PKG_NAME,
StructField,
USIZE,
USIZE_SIZE,
WasmPrim,
WasmTarget,
} from "../api.js";
import {
enumName,
Expand Down Expand Up @@ -173,6 +172,7 @@ import { Pointer, ${__stringImpl(
? [
`(addr) => {`,
...__mapStringArray(
opts.target,
"buf",
$stringImpl,
f.len!,
Expand Down Expand Up @@ -204,16 +204,23 @@ import { Pointer, ${__stringImpl(
);
}
} 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)};`,
`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)};`,
`const addr = ${__ptr(opts.target, offset)};`,
...__mapStringArray(
opts.target,
"buf",
$stringImpl,
"len",
Expand All @@ -222,7 +229,7 @@ import { Pointer, ${__stringImpl(
);
} else {
lines.push(
`const addr = ${__ptr(offset)};`,
`const addr = ${__ptr(opts.target, offset)};`,
...__mapArray(f)
);
}
Expand All @@ -237,6 +244,7 @@ import { Pointer, ${__stringImpl(
`if ($${f.name}) return $${f.name};`,
`const addr = ${__addr(offset)};`,
...__mapStringArray(
opts.target,
f.name,
$stringImpl,
f.len!,
Expand Down Expand Up @@ -340,11 +348,12 @@ const __addrShift = (offset: number, shift: string) => {
};

/** @internal */
const __ptr = (offset: number) => `mem.${USIZE}[${__addrShift(offset, USIZE)}]`;
const __ptr = (target: WasmTarget, offset: number) =>
`mem.${target.usize}[${__addrShift(offset, target.usize)}]`;

/** @internal */
const __ptrShift = (offset: number, shift: string) =>
__ptr(offset) + " >>> " + __shift(shift);
const __ptrShift = (target: WasmTarget, offset: number, shift: string) =>
__ptr(target, offset) + " >>> " + __shift(shift);

const __mem = (type: string, offset: number) =>
`mem.${type}[${__addrShift(offset!, type)}]`;
Expand All @@ -359,6 +368,7 @@ const __mapArray = (f: StructField, len: NumOrString = "len") => [

/** @internal */
const __mapStringArray = (
target: WasmTarget,
name: string,
type: "WasmStringSlice" | "WasmStringPtr",
len: NumOrString,
Expand All @@ -367,7 +377,7 @@ const __mapStringArray = (
) => [
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)
target.usizeBytes * (type === "WasmStringSlice" ? 2 : 1)
}, ${isConst}));`,
`return $${name};`,
];
4 changes: 4 additions & 0 deletions packages/wasm-api/src/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import type { IWasmMemoryAccess, ReadonlyWasmString } from "./api.js";
* Memory mapped string wrapper for Zig-style UTF-8 encoded byte slices (aka
* pointer & length pair). The actual JS string can be obtained via
* {@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)
*/
export class WasmStringSlice implements ReadonlyWasmString {
readonly max: number;
Expand Down

0 comments on commit 62c049b

Please sign in to comment.