diff --git a/src/ast.ts b/src/ast.ts
index 9139997..77de976 100644
--- a/src/ast.ts
+++ b/src/ast.ts
@@ -270,7 +270,13 @@ export type ExprBreak = {
export type ExprStructLiteral
= {
kind: "structLiteral";
name: IdentWithRes
;
- fields: [Ident, Expr
][];
+ fields: StructLiteralField
[];
+};
+
+export type StructLiteralField
= {
+ name: Ident;
+ expr: Expr
;
+ fieldIdx?: number;
};
export type TupleLiteral
= {
@@ -432,7 +438,7 @@ export const BUILTINS = [
"__string_len",
"__memory_size",
"__memory_grow",
- "__i32_extend_to_i64_u"
+ "__i32_extend_to_i64_u",
] as const;
export type BuiltinName = (typeof BUILTINS)[number];
@@ -789,7 +795,10 @@ export function superFoldExpr(
...expr,
kind: "structLiteral",
name: folder.ident(expr.name),
- fields: expr.fields.map(([name, expr]) => [name, folder.expr(expr)]),
+ fields: expr.fields.map(({ name, expr }) => ({
+ name,
+ expr: folder.expr(expr),
+ })),
};
}
case "tupleLiteral": {
diff --git a/src/index.ts b/src/index.ts
index c09e0a6..99c8b49 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -15,10 +15,21 @@ import { Ids } from "./utils";
const INPUT = `
extern mod std;
-type A = { a: String };
+type A = { a: Int };
function main() = (
- std.rt.allocateItem(0_I32, 0_I32);
+ let a = A { a: 100 };
+ printA(a);
+);
+
+function printA(a: A) = (
+ print("ABCDEFGH\\n");
+ std.printlnInt(a.a);
+ print("ABCDEFGH\\n");
+);
+
+function linkStd() = (
+ std.println("a");
);
`;
diff --git a/src/lower.test.ts b/src/lower.test.ts
new file mode 100644
index 0000000..a116f7c
--- /dev/null
+++ b/src/lower.test.ts
@@ -0,0 +1,46 @@
+import { TY_I32, TY_INT, TyStruct } from "./ast";
+import { layoutOfStruct } from "./lower";
+
+it("should compute struct layout correctly", () => {
+ const ty: TyStruct = {
+ kind: "struct",
+ name: "",
+ fields: [
+ ["uwu", TY_I32],
+ ["owo", TY_INT],
+ ],
+ };
+
+ const layout = layoutOfStruct(ty);
+
+ expect(layout).toMatchInlineSnapshot(`
+ {
+ "align": 8,
+ "fields": [
+ {
+ "ty": {
+ "kind": "i32",
+ },
+ "types": [
+ {
+ "offset": 0,
+ "type": "i32",
+ },
+ ],
+ },
+ {
+ "ty": {
+ "kind": "int",
+ },
+ "types": [
+ {
+ "offset": 8,
+ "type": "i64",
+ },
+ ],
+ },
+ ],
+ "size": 16,
+ }
+ `);
+});
diff --git a/src/lower.ts b/src/lower.ts
index e9485ff..e35e419 100644
--- a/src/lower.ts
+++ b/src/lower.ts
@@ -2,6 +2,7 @@ import {
Crate,
Expr,
ExprBlock,
+ Folder,
FunctionDef,
GlobalItem,
ImportDef,
@@ -11,9 +12,13 @@ import {
Resolution,
Ty,
TyFn,
+ TyStruct,
TyTuple,
Typecked,
findCrateItem,
+ mkDefaultFolder,
+ superFoldExpr,
+ superFoldItem,
varUnreachable,
} from "./ast";
import { printTy } from "./printer";
@@ -31,7 +36,7 @@ const WASM_PAGE = 65536;
const DUMMY_IDX = 9999999;
-const ALLOCATE_SYMBOL = "nil__std__rt__allocateItem";
+const ALLOCATE_ITEM: string[] = ["std", "rt", "allocateItem"];
type RelocationKind =
| {
@@ -57,6 +62,7 @@ export type Context = {
globalIndices: ComplexMap;
crates: Crate[];
relocations: Relocation[];
+ knownDefPaths: ComplexMap;
};
function mangleDefPath(defPath: string[]): string {
@@ -95,6 +101,8 @@ function appendData(cx: Context, newData: Uint8Array): number {
});
return 0;
} else {
+ console.log("appending", newData);
+
const data = datas[0];
const idx = data.init.length;
const init = new Uint8Array(data.init.length + newData.length);
@@ -112,7 +120,45 @@ function findItem(cx: Context, id: ItemId): Item {
);
}
+const KNOWN_DEF_PATHS = [ALLOCATE_ITEM];
+
+function getKnownDefPaths(
+ crates: Crate[]
+): ComplexMap {
+ const knows = new ComplexMap();
+
+ const folder: Folder = {
+ ...mkDefaultFolder(),
+ itemInner(item): Item {
+ KNOWN_DEF_PATHS.forEach((path) => {
+ if (JSON.stringify(path) === JSON.stringify(item.defPath)) {
+ knows.set(path, item.id);
+ }
+ });
+
+ return superFoldItem(item, this);
+ },
+ expr(expr) {
+ return superFoldExpr(expr, this);
+ },
+ ident(ident) {
+ return ident;
+ },
+ type(type) {
+ return type;
+ },
+ };
+
+ crates.forEach((crate) =>
+ crate.rootItems.forEach((item) => folder.item(item))
+ );
+
+ return knows;
+}
+
export function lower(crates: Crate[]): wasm.Module {
+ const knownDefPaths = getKnownDefPaths(crates);
+
const mod: wasm.Module = {
types: [],
funcs: [],
@@ -145,6 +191,7 @@ export function lower(crates: Crate[]): wasm.Module {
reservedHeapMemoryStart: 0,
crates,
relocations: [],
+ knownDefPaths,
};
function lowerMod(items: Item[]) {
@@ -304,10 +351,16 @@ type ArgRetAbi = wasm.ValType[];
type VarLocation = { localIdx: number; types: wasm.ValType[] };
+type StructFieldLayout = {
+ types: { offset: number; type: wasm.ValType }[];
+ ty: Ty;
+};
+
type StructLayout = {
- size: number,
- align: number,
-}
+ size: number;
+ align: number;
+ fields: StructFieldLayout[];
+};
function lowerFunc(
cx: Context,
@@ -776,7 +829,53 @@ function lowerExpr(
break;
}
case "struct": {
- todo("struct field accesses");
+ const ty = expr.lhs.ty;
+ const layout = layoutOfStruct(ty);
+ const field = layout.fields[expr.field.fieldIdx!];
+
+ // TODO: SCRATCH LOCALS
+ const ptrLocal = fcx.wasmType.params.length + fcx.wasm.locals.length;
+ fcx.wasm.locals.push("i32");
+
+ // We save the local for getting it later for all the field parts.
+ instrs.push({
+ kind: "local.set",
+ imm: ptrLocal,
+ });
+
+ field.types.forEach((fieldPart) => {
+ instrs.push({
+ kind: "local.get",
+ imm: ptrLocal,
+ });
+ switch (fieldPart.type) {
+ case "i32":
+ instrs.push({
+ kind: "i32.load",
+ imm: {
+ align: sizeOfValtype(fieldPart.type),
+ offset: fieldPart.offset,
+ },
+ });
+ break;
+ case "i64":
+ instrs.push({
+ kind: "i64.load",
+ imm: {
+ align: sizeOfValtype(fieldPart.type),
+ offset: fieldPart.offset,
+ },
+ });
+ break;
+ default: {
+ throw new Error(
+ `unsupported struct content type: ${fieldPart.type}`
+ );
+ }
+ }
+ });
+
+ break;
}
default:
throw new Error("invalid field access lhs");
@@ -849,7 +948,70 @@ function lowerExpr(
break;
}
case "structLiteral": {
- todo("struct literal");
+ if (expr.ty.kind !== "struct") {
+ throw new Error("struct literal must have struct type");
+ }
+ const layout = layoutOfStruct(expr.ty);
+
+ // std.rt.allocateItem(size, align);
+ instrs.push({ kind: "i32.const", imm: BigInt(layout.size) });
+ instrs.push({ kind: "i32.const", imm: BigInt(layout.align) });
+ const allocate: wasm.Instr = { kind: "call", func: DUMMY_IDX };
+ const allocateItemId = fcx.cx.knownDefPaths.get(ALLOCATE_ITEM);
+ if (!allocateItemId) {
+ throw new Error("std.rt.allocateItem not found");
+ }
+ fcx.cx.relocations.push({
+ kind: "funccall",
+ instr: allocate,
+ res: { kind: "item", id: allocateItemId },
+ });
+ instrs.push(allocate);
+ // TODO: scratch locals...
+ const ptrLocal = fcx.wasmType.params.length + fcx.wasm.locals.length;
+ fcx.wasm.locals.push("i32");
+ instrs.push({ kind: "local.set", imm: ptrLocal });
+
+ // Now, set all fields.
+ expr.fields.forEach((field, i) => {
+ instrs.push({ kind: "local.get", imm: ptrLocal });
+ lowerExpr(fcx, instrs, field.expr);
+
+ const fieldLayout = [...layout.fields[i].types];
+ fieldLayout.reverse();
+ fieldLayout.forEach((fieldPart) => {
+ switch (fieldPart.type) {
+ case "i32":
+ instrs.push({
+ kind: "i32.store",
+ imm: {
+ align: sizeOfValtype(fieldPart.type),
+ offset: fieldPart.offset,
+ },
+ });
+ break;
+ case "i64":
+ instrs.push({
+ kind: "i64.store",
+ imm: {
+ align: sizeOfValtype(fieldPart.type),
+ offset: fieldPart.offset,
+ },
+ });
+ break;
+ default: {
+ throw new Error(
+ `unsupported struct content type: ${fieldPart.type}`
+ );
+ }
+ }
+ });
+ });
+
+ // Last, load the pointer and pass that on.
+ instrs.push({ kind: "local.get", imm: ptrLocal });
+
+ break;
}
case "tupleLiteral": {
expr.fields.forEach((field) => lowerExpr(fcx, instrs, field));
@@ -931,7 +1093,7 @@ function argRetAbi(param: Ty): ArgRetAbi {
case "tuple":
return param.elems.flatMap(argRetAbi);
case "struct":
- todo("struct ABI");
+ return ["i32"];
case "never":
return [];
case "var":
@@ -981,7 +1143,7 @@ function wasmTypeForBody(ty: Ty): wasm.ValType[] {
case "fn":
todo("fn types");
case "struct":
- todo("struct types");
+ return ["i32"];
case "never":
return [];
case "var":
@@ -989,6 +1151,70 @@ function wasmTypeForBody(ty: Ty): wasm.ValType[] {
}
}
+function sizeOfValtype(type: wasm.ValType): number {
+ switch (type) {
+ case "i32":
+ case "f32":
+ return 4;
+ case "i64":
+ case "f64":
+ return 8;
+ case "v128":
+ case "funcref":
+ case "externref":
+ throw new Error("types not emitted");
+ }
+}
+
+export function layoutOfStruct(ty: TyStruct): StructLayout {
+ const fieldWasmTys = ty.fields.map(([, field]) => wasmTypeForBody(field));
+
+ const align = fieldWasmTys.some((field) =>
+ field.some((type) => type === "i64")
+ )
+ ? 8
+ : 4;
+
+ let offset = 0;
+
+ const fields: StructFieldLayout[] = fieldWasmTys.map((field, i) => {
+ const value: StructFieldLayout = {
+ types: [],
+ ty: ty.fields[i][1],
+ };
+
+ const types = field.map((type) => {
+ const size = sizeOfValtype(type);
+
+ if (size === 8 && offset % 8 !== 0) {
+ // padding.
+ offset += 4;
+ }
+
+ const fieldPart = {
+ offset,
+ type,
+ };
+ offset += size;
+ return fieldPart;
+ });
+
+ value.types = types;
+
+ return value;
+ });
+
+ if (align === 8 && offset % 8 !== 0) {
+ offset += 4;
+ }
+
+ return {
+ size: offset,
+ align,
+ fields,
+ };
+}
+
function blockTypeForBody(cx: Context, ty: Ty): wasm.Blocktype {
const typeIdx = internFuncType(cx, {
params: [],
diff --git a/src/parser.ts b/src/parser.ts
index 512eb35..fc977f7 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -30,8 +30,9 @@ import {
ExternItem,
ItemId,
GlobalItem,
+ StructLiteralField,
} from "./ast";
-import { CompilerError, DUMMY_SPAN, EOF_SPAN, Span, spanMerge } from "./error";
+import { CompilerError, EOF_SPAN, Span, spanMerge } from "./error";
import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer";
import { ComplexMap, ComplexSet, Ids } from "./utils";
@@ -540,15 +541,19 @@ function parseStructInit(
[t] = expectNext(t, "{");
let fields;
- [t, fields] = parseCommaSeparatedList<[Ident, Expr]>(t, "}", (t) => {
- let name;
- [t, name] = expectNext(t, "identifier");
- [t] = expectNext(t, ":");
- let expr;
- [t, expr] = parseExpr(t);
+ [t, fields] = parseCommaSeparatedList>(
+ t,
+ "}",
+ (t) => {
+ let name;
+ [t, name] = expectNext(t, "identifier");
+ [t] = expectNext(t, ":");
+ let expr;
+ [t, expr] = parseExpr(t);
- return [t, [{ name: name.ident, span: name.span }, expr]];
- });
+ return [t, { name: { name: name.ident, span: name.span }, expr }];
+ }
+ );
return [t, fields];
}
diff --git a/src/printer.ts b/src/printer.ts
index 2101ae9..faf9468 100644
--- a/src/printer.ts
+++ b/src/printer.ts
@@ -187,7 +187,7 @@ function printExpr(expr: Expr, indent: number): string {
}
case "structLiteral": {
return `${printIdent(expr.name)} { ${expr.fields
- .map(([name, expr]) => `${name.name}: ${printExpr(expr, indent + 1)}`)
+ .map(({ name, expr }) => `${name.name}: ${printExpr(expr, indent + 1)}`)
.join(", ")} }`;
}
case "tupleLiteral": {
diff --git a/src/typeck.ts b/src/typeck.ts
index 737f5da..66160c7 100644
--- a/src/typeck.ts
+++ b/src/typeck.ts
@@ -9,7 +9,6 @@ import {
ExprUnary,
foldAst,
Folder,
- Ident,
IdentWithRes,
ItemId,
LOGICAL_KINDS,
@@ -30,6 +29,7 @@ import {
TyStruct,
Item,
findCrateItem,
+ StructLiteralField,
} from "./ast";
import { CompilerError, Span } from "./error";
import { printTy } from "./printer";
@@ -943,8 +943,8 @@ export function checkBody(
};
}
case "structLiteral": {
- const fields = expr.fields.map<[Ident, Expr]>(
- ([name, expr]) => [name, this.expr(expr)]
+ const fields = expr.fields.map>(
+ ({ name, expr }) => ({ name, expr: this.expr(expr) })
);
const structTy = typeOf(expr.name.res, expr.name.span);
@@ -958,16 +958,20 @@ export function checkBody(
const assignedFields = new Set();
- fields.forEach(([name, field]) => {
- const fieldTy = structTy.fields.find((def) => def[0] === name.name);
- if (!fieldTy) {
+ fields.forEach(({ name, expr: field }, i) => {
+ const fieldIdx = structTy.fields.findIndex(
+ (def) => def[0] === name.name
+ );
+ if (fieldIdx == -1) {
throw new CompilerError(
`field ${name.name} doesn't exist on type ${expr.name.name}`,
name.span
);
}
+ const fieldTy = structTy.fields[fieldIdx];
infcx.assign(fieldTy[1], field.ty, field.span);
assignedFields.add(name.name);
+ fields[i].fieldIdx = fieldIdx;
});
const missing: string[] = [];
diff --git a/src/wasm/wat.ts b/src/wasm/wat.ts
index a7710fc..1bf1788 100644
--- a/src/wasm/wat.ts
+++ b/src/wasm/wat.ts
@@ -146,7 +146,7 @@ function printBinaryString(buf: Uint8Array, f: FmtCtx) {
buf.forEach((byte) => {
const noEscape =
- (byte > 0x30 && byte <= 0x5a) || (byte > 0x61 && byte <= 0x71);
+ (byte > 0x30 && byte <= 0x5a) || (byte >= 0x61 && byte <= 0x7a);
if (noEscape) {
parts.push(`${String.fromCharCode(byte)}`);
} else {