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

[V-126] Map type #57

Merged
merged 11 commits into from
Oct 1, 2024
20 changes: 3 additions & 17 deletions src/__tests__/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { describe, test, vi } from "vitest";
import assert from "node:assert";
import { getWasmFn, getWasmInstance } from "../lib/wasm.js";
import * as rCallUtil from "../assembler/return-call.js";
import { readString } from "../lib/read-string.js";

describe("E2E Compiler Pipeline", () => {
test("Compiler can compile and run a basic voyd program", async (t) => {
Expand Down Expand Up @@ -70,6 +71,8 @@ describe("E2E Compiler Pipeline", () => {
"Hello, world! This is a test.",
12, // Array of objects test + advanced match
173, // Array test
4, // Structural object re-assignment
"world",
]);
});

Expand All @@ -80,20 +83,3 @@ describe("E2E Compiler Pipeline", () => {
t.expect(did);
});
});

const readString = (ref: Object, instance: WebAssembly.Instance) => {
const newStringIterator = getWasmFn("new_string_iterator", instance)!;
const readNextChar = getWasmFn("read_next_char", instance)!;
const reader = newStringIterator(ref);

let str = "";
while (true) {
const char = readNextChar(reader);
if (char < 0) {
break;
}
str += String.fromCharCode(char);
}

return str;
};
17 changes: 17 additions & 0 deletions src/__tests__/fixtures/e2e-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,23 @@ pub fn test22()
v.value
None:
-1

// Test structural object re-assignment
pub fn test23()
let val = { x: 1, y: 2, z: 3 }
val.x = 4
val.x

// Test the Map type
pub fn test24()
let map = new_map<string>()
map.set("hello", "world")
map.set("goodbye", "night")
map.get("hello").match(v)
Some<string>:
v.value
None:
"not found"
`;

export const tcoText = `
Expand Down
7 changes: 6 additions & 1 deletion src/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,13 +377,18 @@ const compileFieldAssign = (opts: CompileExprOpts<Call>) => {
const access = expr.callArgAt(0);
const member = access.identifierArgAt(1);
const target = access.exprArgAt(0);
const type = getExprType(target) as ObjectType | IntersectionType;

if (type.getAttribute("isStructural") || type.isIntersectionType()) {
return opts.fieldLookupHelpers.setFieldValueByAccessor(opts);
}

const value = compileExpression({
...opts,
expr: expr.argAt(1)!,
isReturnExpr: false,
});

const type = getExprType(target) as ObjectType;
const index = type.getFieldIndex(member);
if (index === -1) {
throw new Error(`Field ${member} not found in ${type.id}`);
Expand Down
146 changes: 117 additions & 29 deletions src/assembler/field-lookup-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
refFunc,
callRef,
refCast,
structSetFieldValue,
} from "../lib/binaryen-gc/index.js";
import {
IntersectionType,
Expand All @@ -35,27 +36,28 @@ export const initFieldLookupHelpers = (mod: binaryen.Module) => {
name: "FieldAccessor",
fields: [
{ name: "__field_hash", type: bin.i32, mutable: false },
{ name: "__field_accessor", type: bin.funcref, mutable: false },
{ name: "__field_getter", type: bin.funcref, mutable: false },
{ name: "__field_setter", type: bin.funcref, mutable: false },
],
});
const lookupTableType = defineArrayType(mod, fieldAccessorStruct, true);
const LOOKUP_NAME = "__lookup_field_accessor";

mod.addFunction(
LOOKUP_NAME,
// Field hash int, Field lookup table
bin.createType([bin.i32, lookupTableType]),
// Field hash int, Field lookup table, getterOrSetter 0 = getter, 1 = setter
bin.createType([bin.i32, lookupTableType, bin.i32]),
bin.funcref, // Field accessor
[bin.i32], // Current index parameter
mod.block(null, [
mod.local.set(2, mod.i32.const(0)), // Current field index
mod.local.set(3, mod.i32.const(0)), // Current field index
mod.loop(
"loop",
mod.block(null, [
// Trap if we've reached the end of the field table, the compiler messed up
mod.if(
mod.i32.eq(
mod.local.get(2, bin.i32),
mod.local.get(3, bin.i32),
arrayLen(mod, mod.local.get(1, lookupTableType))
),
mod.unreachable()
Expand All @@ -72,34 +74,49 @@ export const initFieldLookupHelpers = (mod: binaryen.Module) => {
exprRef: arrayGet(
mod,
mod.local.get(1, lookupTableType),
mod.local.get(2, bin.i32),
mod.local.get(3, bin.i32),
bin.i32,
false
),
})
),

// If we have return the accessor function
// If we have, return the appropriate getter or setter
mod.return(
structGetFieldValue({
mod,
fieldType: bin.funcref,
fieldIndex: 1,
exprRef: arrayGet(
mod.if(
mod.i32.eq(mod.local.get(2, bin.i32), mod.i32.const(0)),
structGetFieldValue({
mod,
mod.local.get(1, lookupTableType),
mod.local.get(2, bin.i32),
bin.i32,
false
),
})
fieldType: bin.funcref,
fieldIndex: 1,
exprRef: arrayGet(
mod,
mod.local.get(1, lookupTableType),
mod.local.get(3, bin.i32),
fieldAccessorStruct,
false
),
}),
structGetFieldValue({
mod,
fieldType: bin.funcref,
fieldIndex: 2,
exprRef: arrayGet(
mod,
mod.local.get(1, lookupTableType),
mod.local.get(3, bin.i32),
fieldAccessorStruct,
false
),
})
)
)
),

// Increment ancestor index
mod.local.set(
2,
mod.i32.add(mod.local.get(2, bin.i32), mod.i32.const(1))
3,
mod.i32.add(mod.local.get(3, bin.i32), mod.i32.const(1))
),
mod.br("loop"),
])
Expand All @@ -113,10 +130,11 @@ export const initFieldLookupHelpers = (mod: binaryen.Module) => {
mod,
binaryenTypeToHeapType(lookupTableType),
obj.fields.map((field, index) => {
const accessorName = `obj_field_accessor_${obj.id}_${field.name}`;
const getterName = `obj_field_getter_${obj.id}_${field.name}`;
const setterName = `obj_field_setter_${obj.id}_${field.name}`;

const accessor = mod.addFunction(
accessorName,
const getter = mod.addFunction(
getterName,
bin.createType([mapBinaryenType(opts, voydBaseObject)]),
mapBinaryenType(opts, field.type!),
[],
Expand All @@ -132,14 +150,39 @@ export const initFieldLookupHelpers = (mod: binaryen.Module) => {
})
);

const funcHeapType = bin._BinaryenFunctionGetType(accessor);
const funcType = bin._BinaryenTypeFromHeapType(funcHeapType, false);
const setter = mod.addFunction(
setterName,
bin.createType([
mapBinaryenType(opts, voydBaseObject),
mapBinaryenType(opts, field.type!),
]),
bin.none,
[],
structSetFieldValue({
mod,
fieldIndex: index + 2, // Skip RTT type fields
ref: refCast(
mod,
mod.local.get(0, mapBinaryenType(opts, voydBaseObject)),
mapBinaryenType(opts, obj)
),
value: mod.local.get(1, mapBinaryenType(opts, field.type!)),
})
);

const getterHeapType = bin._BinaryenFunctionGetType(getter);
const getterType = bin._BinaryenTypeFromHeapType(getterHeapType, false);

const setterHeapType = bin._BinaryenFunctionGetType(setter);
const setterType = bin._BinaryenTypeFromHeapType(setterHeapType, false);

field.binaryenAccessorType = funcType;
field.binaryenGetterType = getterType;
field.binaryenSetterType = setterType;

return initStruct(mod, fieldAccessorStruct, [
mod.i32.const(murmurHash3(field.name)),
refFunc(mod, accessorName, funcType),
refFunc(mod, getterName, getterType),
refFunc(mod, setterName, setterType),
]);
})
);
Expand Down Expand Up @@ -171,22 +214,67 @@ export const initFieldLookupHelpers = (mod: binaryen.Module) => {

const funcRef = mod.call(
LOOKUP_NAME,
[mod.i32.const(murmurHash3(member.value)), lookupTable],
[mod.i32.const(murmurHash3(member.value)), lookupTable, mod.i32.const(0)],
bin.funcref
);

return callRef(
mod,
refCast(mod, funcRef, field.binaryenAccessorType!),
refCast(mod, funcRef, field.binaryenGetterType!),
[compileExpression({ ...opts, expr: obj })],
mapBinaryenType(opts, field.type!)
);
};

const setFieldValueByAccessor = (opts: CompileExprOpts<Call>) => {
const { expr, mod } = opts;
const access = expr.callArgAt(0);
const member = access.identifierArgAt(1);
const target = access.exprArgAt(0);
const value = compileExpression({
...opts,
expr: expr.argAt(1)!,
isReturnExpr: false,
});
const objType = getExprType(target) as ObjectType | IntersectionType;

const field = objType.isIntersectionType()
? objType.nominalType?.getField(member) ??
objType.structuralType?.getField(member)
: objType.getField(member);

if (!field) {
throw new Error(
`Field ${member.value} not found on object ${objType.id}`
);
}

const lookupTable = structGetFieldValue({
mod,
fieldType: lookupTableType,
fieldIndex: 1,
exprRef: compileExpression({ ...opts, expr: target }),
});

const funcRef = mod.call(
LOOKUP_NAME,
[mod.i32.const(murmurHash3(member.value)), lookupTable, mod.i32.const(1)],
bin.funcref
);

return callRef(
mod,
refCast(mod, funcRef, field.binaryenSetterType!),
[compileExpression({ ...opts, expr: target }), value],
mapBinaryenType(opts, field.type!)
);
};

return {
initFieldIndexTable,
lookupTableType,
LOOKUP_NAME,
getFieldValueByAccessor,
setFieldValueByAccessor,
};
};
19 changes: 19 additions & 0 deletions src/lib/read-string.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { getWasmFn } from "./wasm.js";

/** Read a string returned by a voyd function call */
export const readString = (ref: Object, instance: WebAssembly.Instance) => {
const newStringIterator = getWasmFn("new_string_iterator", instance)!;
const readNextChar = getWasmFn("read_next_char", instance)!;
const reader = newStringIterator(ref);

let str = "";
while (true) {
const char = readNextChar(reader);
if (char < 0) {
break;
}
str += String.fromCharCode(char);
}

return str;
};
2 changes: 1 addition & 1 deletion src/parser/reader-macros/generics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ export const genericsMacro: ReaderMacro = {
},
macro: (file, { reader }) => {
const items = reader(file, ">");
return items.insert("generics");
return items.insert("generics").insert(",", 1);
},
};
6 changes: 6 additions & 0 deletions src/run.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import binaryen from "binaryen";
import { readString } from "./lib/read-string.js";

export function run(mod: binaryen.Module) {
const binary = mod.emitBinary();
Expand All @@ -12,5 +13,10 @@ export function run(mod: binaryen.Module) {
const fns = instance.exports as any;
const result = fns.main();

if (typeof result === "object") {
console.log(readString(result, instance));
return;
}

console.log(result);
}
Loading