Last active
May 4, 2023 07:34
-
-
Save rasmusmerzin/b514e3416311c9acf4dbc3d1911e1ad3 to your computer and use it in GitHub Desktop.
ABI Zig π Typescript
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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")); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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