Skip to content

Commit

Permalink
[V-118] String support (#52)
Browse files Browse the repository at this point in the history
* Renames

* Rename resolve types to resolve entities

* Rename resolve types file

* One more file rename

* Semantics for FixedArray

* Direct fixed array init

* I think it works

* Expose string module

* Maybe working string literal

* It works!

* Bugfix

* Change to namespace

* Remove log

* Add unit test
  • Loading branch information
drew-y authored Sep 24, 2024
1 parent e43191b commit 89abeeb
Show file tree
Hide file tree
Showing 35 changed files with 476 additions and 318 deletions.
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

0 comments on commit 89abeeb

Please sign in to comment.