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

TSX Cli #30

Merged
merged 15 commits into from
Sep 1, 2024
3 changes: 2 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@
"prepublishOnly": "tsc"
},
"bin": {
"voidc": "./dist/cli.js",
"void": "./dist/cli.js"
"voidc": "./dist/cli/cli.js",
"void": "./dist/cli/cli.js",
"vt": "./src/cli/cli-dev.ts"
},
"keywords": [],
"author": "",
Expand Down
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
11 changes: 11 additions & 0 deletions src/cli/cli-dev.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/usr/bin/env tsx
import { exec } from "./exec.js";

/**
* NOTE:
* This file is the same as cli.js, but is run with ts-node.
* I've found tsc -w to be buggy when changing git branches,
* so this lets me bypass the compiler.
*/

exec();
4 changes: 4 additions & 0 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env node
import { exec } from "./exec.js";

exec();
15 changes: 7 additions & 8 deletions src/cli.ts → src/cli/exec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
#!/usr/bin/env node
import { stdout } from "process";
import { getConfig } from "./lib/config/index.js";
import { assemble } from "./assembler.js";
import { run } from "./run.js";
import { processSemantics } from "./semantics/index.js";
import { getConfig } from "../lib/config/index.js";
import { assemble } from "../assembler.js";
import { run } from "../run.js";
import { processSemantics } from "../semantics/index.js";
import binaryen from "binaryen";
import { testGc } from "./lib/binaryen-gc/test.js";
import { parseFile, parseModuleFromSrc } from "./parser/index.js";
import { testGc } from "../lib/binaryen-gc/test.js";
import { parseFile, parseModuleFromSrc } from "../parser/index.js";

main().catch(errorHandler);
export const exec = () => main().catch(errorHandler);

async function main() {
const config = getConfig();
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);
}
Loading