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 {