Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Voi-16] basic nominal object support #24

Merged
merged 14 commits into from
Sep 1, 2024
41 changes: 27 additions & 14 deletions src/__tests__/compiler.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import { e2eVoidText } from "./fixtures/e2e-file.js";
import { e2eVoidText, gcVoidText } from "./fixtures/e2e-file.js";
import { compile } from "../compiler.js";
import { test } from "vitest";
import { describe, test } from "vitest";
import assert from "node:assert";
import { getWasmFn, getWasmInstance } from "../lib/wasm.js";

test("Compiler can compile and run a basic void program", async (t) => {
const mod = await compile(e2eVoidText);
const bin = mod.emitBinary();
const compiled = new WebAssembly.Module(bin);
const instance = new WebAssembly.Instance(compiled);
describe("E2E Compiler Pipeline", () => {
test("Compiler can compile and run a basic void program", async (t) => {
const mod = await compile(e2eVoidText);
const instance = getWasmInstance(mod);
const fn = getWasmFn("main", instance);
assert(fn, "Function exists");
t.expect(fn(), "Main function returns correct value").toEqual(55);
});

const fn =
typeof instance.exports.main === "function"
? instance.exports.main
: undefined;

assert(fn, "Function exists");
t.expect(fn(), "Main function returns correct value").toEqual(55);
test("Compiler can compile gc objects and map correct fns", async (t) => {
const mod = await compile(gcVoidText);
const instance = getWasmInstance(mod);
const test1 = getWasmFn("test1", instance);
const test2 = getWasmFn("test2", instance);
const test3 = getWasmFn("test3", instance);
const test4 = getWasmFn("test4", instance);
assert(test1, "Test1 exists");
assert(test2, "Test2 exists");
assert(test3, "Test3 exists");
assert(test4, "Test4 exists");
t.expect(test1(), "test 1 returns correct value").toEqual(13);
t.expect(test2(), "test 2 returns correct value").toEqual(1);
t.expect(test3(), "test 3 returns correct value").toEqual(2);
t.expect(test4(), "test 4 returns correct value").toEqual(52);
});
});
55 changes: 55 additions & 0 deletions src/__tests__/fixtures/e2e-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,58 @@ pub fn main()
fib(10)

`;

export const gcVoidText = `
use std::all

obj Vec {
x: i32,
y: i32
}

obj Point extends Vec {
x: i32,
y: i32,
z: i32
}

obj Pointy extends Vec {
x: i32,
y: i32,
z: i32
}

fn get_x(vec: Vec)
vec.x

fn get_member(vec: Vec)
vec.y

fn get_member(vec: Point)
vec.z

fn get_member(vec: Pointy)
vec.x

// Should return 13
pub fn test1()
let vec = Point { x: 1, y: 2, z: 13 }
vec.get_member()

// Should return 1
pub fn test2()
let vec = Pointy { x: 1, y: 2, z: 13 }
vec.get_member()

// Should return 2
pub fn test3()
let vec = Vec { x: 1, y: 2 }
vec.get_member()


// Should return 52
pub fn test4()
let vec = Point { x: 52, y: 2, z: 21 }
vec.get_x()

`;
3 changes: 3 additions & 0 deletions src/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,9 @@ const buildObjectType = (
type: mapBinaryenType(mod, field.type!),
name: field.name,
})),
supertype: obj.parentObj
? binaryenTypeToHeapType(mapBinaryenType(mod, obj.parentObj))
: undefined,
});
obj.binaryenType = binaryenType;
return binaryenType;
Expand Down
15 changes: 14 additions & 1 deletion src/lib/binaryen-gc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
HeapTypeRef,
Struct,
Type,
TypeRef,
} from "./types.js";

const bin = binaryen as unknown as AugmentedBinaryen;
Expand Down Expand Up @@ -33,6 +34,12 @@ export const defineStructType = (mod: binaryen.Module, struct: Struct) => {
fields.length
);

if (struct.supertype) {
bin._TypeBuilderSetSubType(typeBuilder, structIndex, struct.supertype);
}

if (!struct.final) bin._TypeBuilderSetOpen(typeBuilder, structIndex);

bin._free(fieldTypesPtr);
bin._free(fieldPackedTypesPtr);
bin._free(fieldMutablesPtr);
Expand All @@ -57,9 +64,15 @@ export const binaryenTypeToHeapType = (type: Type): HeapTypeRef => {
return bin._BinaryenTypeGetHeapType(type);
};

export const refCast = (
mod: binaryen.Module,
ref: ExpressionRef,
type: TypeRef
): ExpressionRef => bin._BinaryenRefCast(mod.ptr, ref, type);

export const initStruct = (
mod: binaryen.Module,
structType: number,
structType: HeapTypeRef,
values: ExpressionRef[]
): ExpressionRef => {
const structNewArgs = allocU32Array(values);
Expand Down
102 changes: 100 additions & 2 deletions src/lib/binaryen-gc/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,97 @@ import {
binaryenTypeToHeapType,
defineStructType,
initStruct,
refCast,
structGetFieldValue,
} from ".//index.js";
} from "./index.js";
import { run } from "../../run.js";

const bin = binaryen as unknown as AugmentedBinaryen;

// Structural sub-typing experiment
export function testGc() {
// Simple module with a function that returns a Vec, and a main function that reads the x value
const mod = new binaryen.Module();
mod.setFeatures(binaryen.Features.All);

// type Object = {};
// type A = { a: i32 };
// type B = { b: i32 };
// type Vec = { a: i32; b: i32 };

const objectType = defineStructType(mod, {
name: "Object",
fields: [],
});

const objectTypeRef = binaryenTypeToHeapType(objectType);

const aType = defineStructType(mod, {
name: "A",
fields: [{ name: "a", type: bin.i32, mutable: false }],
supertype: objectTypeRef,
});

const aTypeRef = binaryenTypeToHeapType(objectType);

const bType = defineStructType(mod, {
name: "B",
fields: [{ name: "b", type: bin.i32, mutable: false }],
supertype: objectTypeRef,
});

const bTypeRef = binaryenTypeToHeapType(bType);

const vecType = defineStructType(mod, {
name: "Vec",
fields: [
{ name: "a", type: bin.i32, mutable: false },
{ name: "b", type: bin.i32, mutable: false },
],
supertype: objectTypeRef,
});

const vecTypeRef = binaryenTypeToHeapType(vecType);

const vec = initStruct(mod, vecTypeRef, [mod.i32.const(1), mod.i32.const(1)]);

mod.addFunction(
"castVec",
bin.createType([bType]),
vecType,
[],
mod.block(null, [refCast(mod, mod.local.get(0, bType), vecType)])
);

mod.addFunction(
"main",
bin.createType([]),
bin.i32,
[bin.anyref],
mod.block(null, [
mod.local.set(0, vec),
structGetFieldValue({
mod,
fieldIndex: 0,
fieldType: bin.i32,
exprRef: refCast(mod, mod.local.get(0, bin.anyref), bType),
}),
])
);

mod.addFunctionExport("main", "main");
mod.autoDrop();
mod.validate();

console.log(mod.emitText());
run(mod);
}

export function testGcOld() {
// Simple module with a function that returns a Vec, and a main function that reads the x value
const mod = new binaryen.Module();
mod.setFeatures(binaryen.Features.All);

const dotType = defineStructType(mod, {
name: "Dot",
fields: [
Expand All @@ -24,6 +105,18 @@ export function testGc() {

const dotTypeRef = binaryenTypeToHeapType(dotType);

const spotType = defineStructType(mod, {
name: "Spot",
fields: [
{ name: "a", type: bin.i32, mutable: false },
{ name: "b", type: bin.i32, mutable: false },
{ name: "c", type: bin.i32, mutable: false },
],
supertype: dotTypeRef,
});

const spotTypeRef = binaryenTypeToHeapType(spotType);

const vecType = defineStructType(mod, {
name: "Vec",
fields: [
Expand All @@ -38,7 +131,11 @@ export function testGc() {
const newStruct = initStruct(mod, vecTypeRef, [
mod.i32.const(1),
mod.i32.const(2),
initStruct(mod, dotTypeRef, [mod.i32.const(1), mod.i32.const(2)]),
initStruct(mod, spotTypeRef, [
mod.i32.const(1),
mod.i32.const(2),
mod.i32.const(2),
]),
]);

// Main function that reads the x value of the Vec
Expand Down Expand Up @@ -70,4 +167,5 @@ export function testGc() {
mod.validate();

console.log(mod.emitText());
run(mod);
}
41 changes: 25 additions & 16 deletions src/lib/binaryen-gc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export type Module = binaryen.Module;
export type Struct = {
name: string;
fields: StructField[];
supertype?: HeapTypeRef;
/** Set to true if the struct cannot be extended */
final?: boolean;
};

export type StructField = {
Expand Down Expand Up @@ -53,11 +56,11 @@ export type AugmentedBinaryen = typeof binaryen & {
elementPackedTyype: PackedType,
elementMutable: bool
): void;
_malloc(size: usize): usize;
_free(ptr: usize): void;
__i32_load(ptr: usize): number;
__i32_store(ptr: usize, value: number): void;
__i32_store8(ptr: usize, value: number): void;
_TypeBuilderSetSubType(
builder: TypeBuilderRef,
index: Index,
supertype: HeapTypeRef
): void;
_TypeBuilderSetStructType(
builder: TypeBuilderRef,
index: Index,
Expand All @@ -66,6 +69,23 @@ export type AugmentedBinaryen = typeof binaryen & {
fieldMutables: ArrayRef<bool>,
numFields: i32
): void;
_TypeBuilderGetTempHeapType(
builder: TypeBuilderRef,
index: Index
): HeapTypeRef;
_TypeBuilderGetSize(builder: TypeBuilderRef): Index;
_TypeBuilderBuildAndDispose(
builder: TypeBuilderRef,
heapTypes: ArrayRef<HeapTypeRef>,
errorIndex: Pointer<Index>,
errorReason: Pointer<TypeBuilderErrorReason>
): bool;
_TypeBuilderSetOpen(builder: TypeBuilderRef, index: Index): void;
_malloc(size: usize): usize;
_free(ptr: usize): void;
__i32_load(ptr: usize): number;
__i32_store(ptr: usize, value: number): void;
__i32_store8(ptr: usize, value: number): void;
_BinaryenStructNew(
module: ModuleRef,
operands: ArrayRef<ExpressionRef>,
Expand All @@ -85,17 +105,6 @@ export type AugmentedBinaryen = typeof binaryen & {
ref: ExpressionRef,
value: ExpressionRef
): ExpressionRef;
_TypeBuilderGetTempHeapType(
builder: TypeBuilderRef,
index: Index
): HeapTypeRef;
_TypeBuilderGetSize(builder: TypeBuilderRef): Index;
_TypeBuilderBuildAndDispose(
builder: TypeBuilderRef,
heapTypes: ArrayRef<HeapTypeRef>,
errorIndex: Pointer<Index>,
errorReason: Pointer<TypeBuilderErrorReason>
): bool;
allocateUTF8OnStack: (s: string) => number;
_BinaryenArrayNew(
module: ModuleRef,
Expand Down
17 changes: 17 additions & 0 deletions src/lib/wasm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import binaryen from "binaryen";

export const getWasmInstance = (
mod: Uint8Array | binaryen.Module
): WebAssembly.Instance => {
const bin = mod instanceof Uint8Array ? mod : mod.emitBinary();
const compiled = new WebAssembly.Module(bin);
return new WebAssembly.Instance(compiled);
};

export const getWasmFn = (
name: string,
instance: WebAssembly.Instance
): Function | undefined => {
const fn = instance.exports[name];
return typeof fn === "function" ? fn : undefined;
};
Loading