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-118] String support #52

Merged
merged 14 commits into from
Sep 24, 2024
27 changes: 27 additions & 0 deletions src/__tests__/compiler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ describe("E2E Compiler Pipeline", () => {
expectedValues.forEach((v, i) => {
const test = getWasmFn(`test${i + 1}`, instance);
assert(test, `Test${i + 1} exists`);

if (typeof v === "string") {
t.expect(
readString(test(), instance),
`test ${i + 1} returns correct value`
).toEqual(v);
return;
}

t.expect(test(), `test ${i + 1} returns correct value`).toEqual(v);
});

Expand All @@ -58,6 +67,7 @@ describe("E2E Compiler Pipeline", () => {
42,
2, // IntersectionType tests
20, // While loop
"Hello, world! This is a test.",
]);
});

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

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

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

return str;
};
11 changes: 7 additions & 4 deletions src/__tests__/fixtures/e2e-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ pub fn main()
`;

export const kitchenSink = `
use std::all
pub use std::string::all

obj Vec {
x: i32,
Expand Down Expand Up @@ -87,15 +87,15 @@ pub fn test7()
let vec = Bitly { x: 52, y: 2, z: 21 }
get_num_from_vec_sub_obj(vec)

type DsArrayI32 = DsArray<i32>
type FixedArrayI32 = FixedArray<i32>

// Test generic functions, should return 143
pub fn test8()
let arr2 = ds_array_init<f64>(10)
let arr2 = new_fixed_array<f64>(10)
arr2.set<f64>(0, 1.5)
arr2.get<f64>(0)

let arr: DsArrayI32 = ds_array_init<i32>(10)
let arr: FixedArrayI32 = new_fixed_array<i32>(10)
arr.set<i32>(9, 143)
arr.get<i32>(9)

Expand Down Expand Up @@ -231,6 +231,9 @@ pub fn test19() -> i32
i = i + 1
if i == 5 then: break
x

pub fn test20() -> String
"Hello, world!" + " " + "This is a test."
`;

export const tcoText = `
Expand Down
76 changes: 62 additions & 14 deletions src/assembler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
Type,
Primitive,
ObjectType,
DsArrayType,
FixedArrayType,
voydBaseObject,
UnionType,
IntersectionType,
Expand All @@ -33,6 +33,7 @@ import { initExtensionHelpers } from "./assembler/extension-helpers.js";
import { returnCall } from "./assembler/return-call.js";
import { Float } from "./syntax-objects/float.js";
import { initFieldLookupHelpers } from "./assembler/field-lookup-helpers.js";
import { List } from "./syntax-objects/list.js";

export const assemble = (ast: Expr) => {
const mod = new binaryen.Module();
Expand Down Expand Up @@ -126,10 +127,20 @@ const compileType = (opts: CompileExprOpts<Type>) => {
};

const compileModule = (opts: CompileExprOpts<VoidModule>) => {
return opts.mod.block(
const result = opts.mod.block(
opts.expr.id,
opts.expr.value.map((expr) => compileExpression({ ...opts, expr }))
);

if (opts.expr.isIndex) {
opts.expr.getAllExports().forEach((entity) => {
if (entity.isFn()) {
opts.mod.addFunctionExport(entity.id, entity.name.value);
}
});
}

return result;
};

const compileBlock = (opts: CompileExprOpts<Block>) => {
Expand Down Expand Up @@ -225,6 +236,7 @@ const compileCall = (opts: CompileExprOpts<Call>): number => {
if (expr.calls("member-access")) return compileObjMemberAccess(opts);
if (expr.calls("while")) return compileWhile(opts);
if (expr.calls("break")) return mod.br(opts.loopBreakId!);
if (expr.calls("FixedArray")) return compileFixedArray(opts);
if (expr.calls("binaryen")) {
return compileBnrCall(opts);
}
Expand All @@ -251,6 +263,15 @@ const compileCall = (opts: CompileExprOpts<Call>): number => {
return mod.call(id, args, returnType);
};

const compileFixedArray = (opts: CompileExprOpts<Call>) => {
const type = opts.expr.type as FixedArrayType;
return gc.arrayNewFixed(
opts.mod,
gc.binaryenTypeToHeapType(mapBinaryenType(opts, type)),
opts.expr.argArrayMap((expr) => compileExpression({ ...opts, expr }))
);
};

const compileWhile = (opts: CompileExprOpts<Call>) => {
const { expr, mod } = opts;
const loopId = expr.syntaxId.toString();
Expand Down Expand Up @@ -309,21 +330,21 @@ const compileObjectInit = (opts: CompileExprOpts<Call>) => {
const compileExport = (opts: CompileExprOpts<Call>) => {
const expr = opts.expr.exprArgAt(0);
const result = compileExpression({ ...opts, expr });

if (expr.parentModule?.isIndex && expr.isBlock()) {
expr.getAllEntities().forEach((entity) => {
if (entity.isFn()) {
opts.mod.addFunctionExport(entity.id, entity.name.value);
}
});
}

return result;
};

const compileAssign = (opts: CompileExprOpts<Call>): number => {
const { expr, mod } = opts;
const identifier = expr.argAt(0) as Identifier;
const identifier = expr.argAt(0);

if (identifier?.isCall()) {
return compileFieldAssign(opts);
}

if (!identifier?.isIdentifier()) {
throw new Error(`Invalid assignment target ${identifier}`);
}

const value = compileExpression({
...opts,
expr: expr.argAt(1)!,
Expand All @@ -341,6 +362,32 @@ const compileAssign = (opts: CompileExprOpts<Call>): number => {
throw new Error(`${identifier} cannot be re-assigned`);
};

const compileFieldAssign = (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 type = getExprType(target) as ObjectType;
const index = type.getFieldIndex(member);
if (index === -1) {
throw new Error(`Field ${member} not found in ${type.id}`);
}
const memberIndex = type.getFieldIndex(member) + OBJECT_FIELDS_OFFSET;

return gc.structSetFieldValue({
mod,
ref: compileExpression({ ...opts, expr: target }),
fieldIndex: memberIndex,
value,
});
};

const compileBnrCall = (opts: CompileExprOpts<Call>): number => {
const { expr } = opts;
const funcId = expr.labeledArgAt(0) as Identifier;
Expand Down Expand Up @@ -505,15 +552,15 @@ export const mapBinaryenType = (
if (isPrimitiveId(type, "voyd")) return binaryen.none;
if (type.isObjectType()) return buildObjectType(opts, type);
if (type.isUnionType()) return buildUnionType(opts, type);
if (type.isDsArrayType()) return buildDsArrayType(opts, type);
if (type.isFixedArrayType()) return buildFixedArrayType(opts, type);
if (type.isIntersectionType()) return buildIntersectionType(opts, type);
throw new Error(`Unsupported type ${type}`);
};

const isPrimitiveId = (type: Type, id: Primitive) =>
type.isPrimitiveType() && type.name.value === id;

const buildDsArrayType = (opts: CompileExprOpts, type: DsArrayType) => {
const buildFixedArrayType = (opts: CompileExprOpts, type: FixedArrayType) => {
if (type.binaryenType) return type.binaryenType;
const mod = opts.mod;
const elemType = mapBinaryenType(opts, type.elemType!);
Expand Down Expand Up @@ -578,6 +625,7 @@ const buildObjectType = (opts: MapBinTypeOpts, obj: ObjectType): TypeRef => {
...obj.fields.map((field) => ({
type: mapBinaryenType(opts, field.type!),
name: field.name,
mutable: true,
})),
],
supertype: obj.parentObjType
Expand Down
34 changes: 31 additions & 3 deletions src/lib/binaryen-gc/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,21 @@ export const defineStructType = (
)
);
const fieldMutablesPtr = allocU32Array(
fields.map(({ mutable }) => (mutable ? 1 : 0))
fields.reduce((acc, { mutable }, index) => {
// Calculate which u32 slot this boolean belongs to
const u32Index = Math.floor(index / 4);

// Ensure the slot exists and initialize it to 0 if it doesn't
if (typeof acc[u32Index] === "undefined") {
acc[u32Index] = 0;
}

// Pack the boolean into the appropriate position in the u32
const shiftAmount = (index % 4) * 8;
acc[u32Index] |= (mutable ? 1 : 0) << shiftAmount;

return acc;
}, [] as number[])
);

bin._TypeBuilderSetStructType(
Expand Down Expand Up @@ -187,6 +201,20 @@ export const structGetFieldValue = ({
);
};

export const structSetFieldValue = ({
mod,
fieldIndex,
ref,
value,
}: {
mod: binaryen.Module;
fieldIndex: number;
ref: ExpressionRef;
value: ExpressionRef;
}): ExpressionRef => {
return bin._BinaryenStructSet(mod.ptr, fieldIndex, ref, value);
};

export const arrayGet = (
mod: binaryen.Module,
arrayRef: ExpressionRef,
Expand Down Expand Up @@ -216,10 +244,10 @@ export const arrayLen = (
export const arrayNew = (
mod: binaryen.Module,
type: HeapTypeRef,
initialLength: ExpressionRef,
size: ExpressionRef,
init: ExpressionRef
): ExpressionRef => {
return bin._BinaryenArrayNew(mod.ptr, type, initialLength, init);
return bin._BinaryenArrayNew(mod.ptr, type, size, init);
};

export const arrayNewFixed = (
Expand Down
1 change: 0 additions & 1 deletion src/lib/host-runtime/bool-to-int.ts

This file was deleted.

1 change: 0 additions & 1 deletion src/lib/host-runtime/index.ts

This file was deleted.

77 changes: 0 additions & 77 deletions src/lib/host-runtime/strings.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/parser/__tests__/__snapshots__/parser.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -819,7 +819,7 @@ exports[`parser supports generics 1`] = `
"=",
"arr",
[
"ds_array_init",
"new_fixed_array",
[
"generics",
"i32",
Expand Down
2 changes: 1 addition & 1 deletion src/parser/__tests__/fixtures/voyd-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ use std::all
type DsArrayi32 = DsArray<i32>

pub fn main()
let arr = ds_array_init<i32>(10)
let arr = new_fixed_array<i32>(10)
arr.set<i32>(0, 1)
arr.get<i32>(0)
`;
Loading