diff --git a/src/ast.ts b/src/ast.ts index fd74c0c..0b7f7c0 100644 --- a/src/ast.ts +++ b/src/ast.ts @@ -81,6 +81,9 @@ export class ItemId { } toString(): string { + if (this.crateId === 0) { + return `${this.itemIdx}`; + } return `[${this.crateId}@${this.itemIdx}]`; } } @@ -105,6 +108,10 @@ export type ItemKind

= | { kind: "extern"; node: ExternItem; + } + | { + kind: "global"; + node: GlobalItem

; }; export type Item

= ItemKind

& { @@ -153,6 +160,13 @@ export type ModItem

= { export type ExternItem = { name: string }; +export type GlobalItem

= { + name: string; + type: Type

; + init: Expr

; + ty?: Ty; +}; + export type ExprEmpty = { kind: "empty" }; export type ExprLet

= { @@ -634,6 +648,17 @@ export function superFoldItem( case "extern": { return { ...item, kind: "extern" }; } + case "global": { + return { + ...item, + kind: "global", + node: { + name: item.node.name, + type: folder.type(item.node.type), + init: folder.expr(item.node.init), + }, + }; + } } } diff --git a/src/index.ts b/src/index.ts index 124895f..0e7edc1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,10 +13,10 @@ import { Crate, Built, Typecked } from "./ast"; import { Ids } from "./utils"; const INPUT = ` -extern mod std; +global HELLO: I32 = 0_I32; function main() = ( - std.printlnInt(10000); + HELLO = 1_I32; ); `; diff --git a/src/lexer.ts b/src/lexer.ts index 902c923..f6d0dda 100644 --- a/src/lexer.ts +++ b/src/lexer.ts @@ -12,6 +12,7 @@ export type DatalessToken = | "import" | "extern" | "mod" + | "global" | "(" | ")" | "{" @@ -316,6 +317,7 @@ const KEYOWRDS: DatalessToken[] = [ "import", "extern", "mod", + "global", ]; const KEYWORD_SET = new Set(KEYOWRDS); diff --git a/src/lower.ts b/src/lower.ts index 15adbd2..dc78b97 100644 --- a/src/lower.ts +++ b/src/lower.ts @@ -3,8 +3,10 @@ import { Expr, ExprBlock, FunctionDef, + GlobalItem, ImportDef, Item, + ItemId, LoopId, Resolution, Ty, @@ -13,6 +15,7 @@ import { Typecked, varUnreachable, } from "./ast"; +import { printTy } from "./printer"; import { ComplexMap, encodeUtf8, unwrap } from "./utils"; import * as wasm from "./wasm/defs"; @@ -25,10 +28,19 @@ const STRING_ABI: ArgRetAbi = STRING_TYPES; const WASM_PAGE = 65536; -type Relocation = { - kind: "funccall"; - instr: wasm.Instr & { func: wasm.FuncIdx }; -} & { res: Resolution }; +const DUMMY_IDX = 9999999; + +type RelocationKind = + | { + kind: "funccall"; + instr: wasm.Instr & { func: wasm.FuncIdx }; + } + | { + kind: "globalref"; + instr: wasm.Instr & { imm: wasm.GlobalIdx }; + }; + +type Relocation = RelocationKind & { res: Resolution }; type FuncOrImport = | { kind: "func"; idx: wasm.FuncIdx } @@ -39,6 +51,7 @@ export type Context = { funcTypes: ComplexMap; reservedHeapMemoryStart: number; funcIndices: ComplexMap; + globalIndices: ComplexMap; crates: Crate[]; relocations: Relocation[]; }; @@ -89,6 +102,12 @@ function appendData(cx: Context, newData: Uint8Array): number { } } +function findItem(cx: Context, id: ItemId): Item { + return unwrap( + unwrap(cx.crates.find((crate) => crate.id === id.crateId)).itemsById.get(id) + ); +} + export function lower(crates: Crate[]): wasm.Module { const mod: wasm.Module = { types: [], @@ -118,6 +137,7 @@ export function lower(crates: Crate[]): wasm.Module { mod, funcTypes: new ComplexMap(), funcIndices: new ComplexMap(), + globalIndices: new ComplexMap(), reservedHeapMemoryStart: 0, crates, relocations: [], @@ -136,6 +156,17 @@ export function lower(crates: Crate[]): wasm.Module { } case "mod": { lowerMod(item.node.contents); + break; + } + case "global": { + lowerGlobal(cx, item, item.node); + break; + } + case "extern": + case "type": + break; + default: { + const _: never = item; } } }); @@ -162,6 +193,20 @@ export function lower(crates: Crate[]): wasm.Module { ); } rel.instr.func = idx.kind === "func" ? offset + idx.idx : idx.idx; + break; + } + case "globalref": { + const idx = cx.globalIndices.get(rel.res); + if (idx === undefined) { + throw new Error( + `no global found for relocation '${JSON.stringify(rel.res)}'` + ); + } + rel.instr.imm = idx; + break; + } + default: { + const _: never = rel; } } }); @@ -201,6 +246,43 @@ function lowerImport( cx.funcIndices.set({ kind: "item", id: item.id }, { kind: "import", idx }); } +function lowerGlobal( + cx: Context, + item: Item, + def: GlobalItem +) { + const globalIdx = cx.mod.globals.length; + + let valtype: "i32" | "i64"; + switch (def.init.ty.kind) { + case "i32": + valtype = "i32"; + break; + case "int": + valtype = "i64"; + break; + default: + throw new Error(`invalid global ty: ${printTy(def.init.ty)}`); + } + + if (def.init.kind !== "literal" || def.init.value.kind !== "int") { + throw new Error(`invalid global init: ${JSON.stringify(def)}`); + } + + const init: wasm.Instr = { + kind: `${valtype}.const`, + imm: def.init.value.value, + }; + + cx.mod.globals.push({ + _name: mangleDefPath(item.defPath), + type: { type: valtype, mut: "var" }, + init: [init], + }); + + cx.globalIndices.set({ kind: "item", id: item.id }, globalIdx); +} + type FuncContext = { cx: Context; item: Item; @@ -304,7 +386,18 @@ function lowerExpr( break; } case "item": { - throw new Error("cannot store to item"); + const item = findItem(fcx.cx, res.id); + if (item.kind !== "global") { + throw new Error("cannot store to non-global item"); + } + + const instr: wasm.Instr = { kind: "global.set", imm: DUMMY_IDX }; + const rel: Relocation = { kind: "globalref", instr, res }; + + fcx.cx.relocations.push(rel); + instrs.push(instr); + + break; } case "builtin": { throw new Error("cannot store to builtin"); @@ -374,8 +467,23 @@ function lowerExpr( loadVariable(instrs, location); break; } - case "item": - todo("item ident res"); + case "item": { + const item = findItem(fcx.cx, res.id); + switch (item.kind) { + case "global": { + const instr: wasm.Instr = { kind: "global.get", imm: DUMMY_IDX }; + const rel: Relocation = { kind: "globalref", instr, res }; + instrs.push(instr); + fcx.cx.relocations.push(rel); + break; + } + default: { + todo("non-global item ident res"); + } + } + + break; + } case "builtin": switch (res.name) { case "false": @@ -561,7 +669,7 @@ function lowerExpr( } } - const callInstr: wasm.Instr = { kind: "call", func: 9999999999 }; + const callInstr: wasm.Instr = { kind: "call", func: DUMMY_IDX }; fcx.cx.relocations.push({ kind: "funccall", instr: callInstr, @@ -865,7 +973,7 @@ function addRt(cx: Context, crates: Crate[]) { const crate0 = unwrap(crates.find((crate) => crate.id === 0)); - const mainCall: wasm.Instr = { kind: "call", func: 9999999 }; + const mainCall: wasm.Instr = { kind: "call", func: DUMMY_IDX }; cx.relocations.push({ kind: "funccall", instr: mainCall, diff --git a/src/parser.ts b/src/parser.ts index 2783ca0..8976efd 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -29,6 +29,7 @@ import { Parsed, ExternItem, ItemId, + GlobalItem, } from "./ast"; import { CompilerError, Span, spanMerge } from "./error"; import { BaseToken, Token, TokenIdent, TokenLitString } from "./lexer"; @@ -180,6 +181,24 @@ function parseItem(t: Token[]): [Token[], Item] { }; return [t, { kind: "mod", node, span: name.span, id: ItemId.dummy() }]; + } else if (tok.kind === "global") { + let name; + [t, name] = expectNext(t, "identifier"); + [t] = expectNext(t, ":"); + let type; + [t, type] = parseType(t); + [t] = expectNext(t, "="); + let init; + [t, init] = parseExpr(t); + [t] = expectNext(t, ";"); + + const node: GlobalItem = { + name: name.ident, + type, + init, + }; + + return [t, { kind: "global", node, span: name.span, id: ItemId.dummy() }]; } else { unexpectedToken(tok, "item"); } diff --git a/src/printer.ts b/src/printer.ts index 76ea7cf..2101ae9 100644 --- a/src/printer.ts +++ b/src/printer.ts @@ -24,7 +24,7 @@ function printStringLiteral(lit: StringLiteral): string { } function printItem(item: Item): string { - const id = `/*${item.id}*/ `; + const id = `/*${item.id.toString()}*/ `; switch (item.kind) { case "function": { @@ -42,6 +42,15 @@ function printItem(item: Item): string { case "extern": { return id + `extern mod ${item.node.name};`; } + case "global": { + return ( + id + + `global ${item.node.name}: ${printType(item.node.type)} = ${printExpr( + item.node.init, + 0 + )};` + ); + } } } @@ -207,7 +216,7 @@ function printRes(res: Resolution): string { case "local": return `#${res.index}`; case "item": - return `#G${res.id}`; + return `#I${res.id.toString()}`; case "builtin": { return `#B`; } diff --git a/src/typeck.ts b/src/typeck.ts index 2d2dc75..b9f8ebe 100644 --- a/src/typeck.ts +++ b/src/typeck.ts @@ -120,6 +120,7 @@ export function typeck( otherCrates: Crate[] ): Crate { const itemTys = new ComplexMap(); + function typeOfItem(itemId: ItemId, cause: Span): Ty { if (itemId.crateId !== ast.id) { console.log(otherCrates); @@ -132,6 +133,7 @@ export function typeck( case "function": case "import": case "type": + case "global": return item.node.ty!; case "mod": { throw new CompilerError( @@ -203,6 +205,11 @@ export function typeck( cause ); } + case "global": { + const ty = lowerAstTy(item.node.type); + itemTys.set(item.id, ty); + return ty; + } } } @@ -227,13 +234,24 @@ export function typeck( ); } + function findItem(itemId: ItemId): Item { + if (itemId.crateId === ast.id) { + return unwrap(ast.itemsById.get(itemId)); + } + return unwrap( + unwrap( + otherCrates.find((crate) => crate.id === itemId.crateId) + ).itemsById.get(itemId) + ); + } + const checker: Folder = { ...mkDefaultFolder(), itemInner(item: Item): Item { switch (item.kind) { case "function": { const fnTy = typeOfItem(item.id, item.span) as TyFn; - const body = checkBody(item.node.body, fnTy, typeOfItem); + const body = checkBody(item.node.body, fnTy, typeOfItem, findItem); const returnType = item.node.returnType && { ...item.node.returnType, @@ -349,6 +367,30 @@ export function typeck( node: { ...item.node }, }; } + case "global": { + const ty = typeOfItem(item.id, item.span); + const { init } = item.node; + + if (init.kind !== "literal" || init.value.kind !== "int") { + throw new CompilerError( + "globals must be initialized with an integer literal", + init.span + ); + } + + const initTy = init.value.type === "I32" ? TY_I32 : TY_INT; + const infcx = new InferContext(); + infcx.assign(ty, initTy, init.span); + + return { + ...item, + node: { + ...item.node, + ty, + init: { ...init, ty }, + }, + }; + } } }, expr(_expr) { @@ -560,7 +602,8 @@ export class InferContext { export function checkBody( body: Expr, fnTy: TyFn, - typeOfItem: (itemId: ItemId, cause: Span) => Ty + typeOfItem: (itemId: ItemId, cause: Span) => Ty, + findItem: (itemId: ItemId) => Item ): Expr { const localTys = [...fnTy.params]; const loopState: { hasBreak: boolean; loopId: LoopId }[] = []; @@ -644,11 +687,26 @@ export function checkBody( infcx.assign(lhs.ty, rhs.ty, expr.span); switch (lhs.kind) { - case "ident": - if (lhs.value.res.kind !== "local") { - throw new CompilerError("cannot assign to items", expr.span); + case "ident": { + const { res } = lhs.value; + switch (res.kind) { + case "local": + break; + case "item": { + const item = findItem(res.id); + if (item.kind !== "global") { + throw new CompilerError("cannot assign to item", expr.span); + } + break; + } + case "builtin": + throw new CompilerError( + "cannot assign to builtins", + expr.span + ); } break; + } default: { throw new CompilerError( "invalid left-hand side of assignment", diff --git a/std.nil b/std.nil index 2692372..95d99d2 100644 --- a/std.nil +++ b/std.nil @@ -57,3 +57,9 @@ function println(s: String) = ( print(s); print("\n"); ); + +mod alloc ( + function allocateItem(size: I32): I32 = ( + + ); +) \ No newline at end of file diff --git a/test.nil b/test.nil index f169233..1b59e0d 100644 --- a/test.nil +++ b/test.nil @@ -1,69 +1,5 @@ -mod owo (); +extern mod std; function main() = ( - prIntln(0); - prIntln(1); - prIntln(9); - prIntln(2352353); - prIntln(100); -); - -function prIntln(x: Int) = ( - prInt(x); - print("\n"); -); - -function stringForDigit(x: Int): String = - if x == 0 then "0" - else if x == 1 then "1" - else if x == 2 then "2" - else if x == 3 then "3" - else if x == 4 then "4" - else if x == 5 then "5" - else if x == 6 then "6" - else if x == 7 then "7" - else if x == 8 then "8" - else if x == 9 then "9" - else trap(); - -function log10(x: Int): Int = ( - let i = 0; - loop ( - if x < 10 then break; - i = i + 1; - x = x / 10; - ); - i -); - -function pow(base: Int, exp: Int): Int = ( - let acc = 1; - loop ( - if exp == 0 then break; - acc = acc * base; - exp = exp - 1; - ); - acc -); - -function prInt(x: Int) = ( - let mag = log10(x); - - loop ( - if mag == 0 then break; - let base = pow(10, mag); - - let digit = x / base; - print(stringForDigit(digit)); - - x = x % base; - mag = mag - 1; - ); - - print(stringForDigit(x % 10)); -); - -function println(s: String) = ( - print(s); - print("\n"); -); + std.printInt(10); +); \ No newline at end of file