Skip to content

Instantly share code, notes, and snippets.

@rasmusmerzin
Last active May 4, 2023 07:34
Show Gist options
  • Save rasmusmerzin/b514e3416311c9acf4dbc3d1911e1ad3 to your computer and use it in GitHub Desktop.
Save rasmusmerzin/b514e3416311c9acf4dbc3d1911e1ad3 to your computer and use it in GitHub Desktop.
ABI Zig πŸ”— Typescript
// see https://gitlab.com/merzin/mono/-/tree/mono/abi
const std = @import("std");
pub fn build(b: *std.build.Builder) void {
const mode = b.standardReleaseOptions();
const lib = b.addSharedLibrary("index", "index.zig", b.version(0, 0, 0));
lib.setBuildMode(mode);
lib.setTarget(.{ .cpu_arch = .wasm32, .os_tag = .freestanding });
lib.setOutputDir("zig-out");
b.default_step.dependOn(&lib.step);
}
import { ABI, instantiate } from ".";
import { resolve } from "path";
import { readFileSync } from "fs";
const source = readFileSync(resolve(__dirname, "zig-out/index.wasm"));
let abi: ABI;
beforeEach(async () => {
abi = await instantiate(ABI, source);
});
it("bytes", () => {
const buf = Uint8Array.from([1, 2, 3]);
const ptr = abi.putBytes(buf);
expect(abi.getBytes(ptr)).toStrictEqual(buf);
abi.free(ptr);
});
it("string", () => {
const str = "hello";
const ptr = abi.putString(str);
expect(abi.getString(ptr)).toBe(str);
abi.free(ptr);
});
// requires ReleaseSafe or Debug mode build
it("double free", () => {
const ptr = abi.putBytes(Uint8Array.from([]));
abi.free(ptr);
expect(() => abi.free(ptr)).toThrow();
});
// requires ReleaseSafe or Debug mode build
it("use after free", () => {
const ptr = abi.putBytes(Uint8Array.from([]));
abi.free(ptr);
expect(() => abi.getBytes(ptr)).toThrow();
});
it("modify", () => {
const ptr = abi.putString("GET");
abi.getBytes(ptr)[0] = "P".charCodeAt(0);
abi.getBytes(ptr)[1] = "U".charCodeAt(0);
expect(abi.getString(ptr)).toBe("PUT");
abi.free(ptr);
});
const abi = @import("index.zig");
const std = @import("std");
const builtin = @import("builtin");
const native_endian = builtin.target.cpu.arch.endian();
const expect = std.testing.expect;
const eql = std.mem.eql;
test "len" {
var ptr = abi.alloc(1466);
defer abi.free(ptr);
try expect(abi.len(ptr) == 1466);
}
test "deref" {
var ptr = abi.alloc(3);
defer abi.free(ptr);
ptr[1] = switch (native_endian) {
.Big => 0x47455400,
.Little => 0x00544547,
};
try expect(eql(u8, abi.deref(ptr), "GET"));
}
test "alloc" {
const ptr = abi.alloc(3);
defer abi.free(ptr);
abi.data(ptr)[0] = 0x50;
abi.data(ptr)[1] = 0x55;
abi.data(ptr)[2] = 0x54;
try expect(abi.len(ptr) == 3);
try expect(eql(u8, abi.deref(ptr), "PUT"));
}
export type Ptr = number;
export interface ABIConstructor<A extends ABI> {
new (exports: any): A;
}
export async function instantiate<A extends ABI>(
cls: ABIConstructor<A>,
source: BufferSource
): Promise<A> {
const result = await WebAssembly.instantiate(source);
return new cls(result.instance.exports);
}
export class ABI {
protected readonly memory!: WebAssembly.Memory;
protected readonly alloc!: (length: number) => Ptr;
protected readonly len!: (ptr: Ptr) => number;
protected readonly LENGTH_WIDTH: number;
protected readonly u8a = (ptr: Ptr, size: number): Uint8Array =>
new Uint8Array(this.memory.buffer, ptr, size);
protected readonly u8 = (ptr: Ptr): number => this.u8a(ptr, 1)[0]!;
readonly free!: (ptr: Ptr) => void;
constructor(exports: any) {
Object.assign(this, exports);
this.LENGTH_WIDTH = this.u8(exports.LENGTH_WIDTH.value);
}
readonly size = (): number => this.memory.buffer.byteLength;
readonly getBytes = (ptr: Ptr): Uint8Array =>
this.u8a(ptr + this.LENGTH_WIDTH, this.len(ptr));
readonly putBytes = (bytes: Uint8Array): Ptr => {
const length = bytes.byteLength;
const ptr = this.alloc(length);
this.u8a(ptr + this.LENGTH_WIDTH, length).set(bytes);
return ptr;
};
readonly getString = (ptr: Ptr): string =>
new TextDecoder().decode(this.getBytes(ptr));
readonly putString = (str: string): Ptr =>
this.putBytes(new TextEncoder().encode(str));
}
const std = @import("std");
const allocator = std.heap.page_allocator;
pub const Length = u32;
pub const Shared = [*]Length;
pub export const LENGTH_WIDTH: u8 = @sizeOf(Length);
pub export fn len(ptr: Shared) Length {
return ptr[0];
}
pub inline fn data(ptr: Shared) [*]u8 {
return @intToPtr([*]u8, @ptrToInt(ptr) + LENGTH_WIDTH);
}
pub inline fn deref(ptr: Shared) []u8 {
return data(ptr)[0..len(ptr)];
}
inline fn size(length: Length) usize {
const data_size = @intToFloat(f32, length) / @intToFloat(f32, LENGTH_WIDTH);
return @floatToInt(usize, 1 + @ceil(data_size));
}
pub export fn alloc(length: Length) Shared {
var buf: []Length = allocator.alloc(Length, size(length)) catch unreachable;
buf[0] = length;
return buf.ptr;
}
// should be renamed when including c libs
pub export fn free(ptr: Shared) void {
allocator.free(ptr[0..size(len(ptr))]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment