import * as ast from "@slige/ast"; import { Checker } from "@slige/check"; import { AstId, Ctx, exhausted, IdMap, Ids, Res, todo } from "@slige/common"; import { Resols } from "@slige/resolve"; import { Ty } from "@slige/ty"; import { BinaryType, Operand, ProjElem, StmtKind, TerKind } from "./mir.ts"; import { Block, BlockId, Fn, Local, LocalId, RVal } from "./mir.ts"; import { MirFnStringifyer } from "@slige/stringify"; export class AstLowerer implements ast.Visitor { private fns = new IdMap(); public constructor( private ctx: Ctx, private re: Resols, private ch: Checker, private ast: ast.File, ) {} public lower() { ast.visitFile(this, this.ast); } visitFnItem(item: ast.Item, kind: ast.FnItem): ast.VisitRes { const fn = new FnLowerer(this.ctx, this.re, this.ch, item, kind) .lower(); if (fn.ok) { this.fns.set(item.id, fn.val); } } public mirString(): string { return this.fns.values() .map((fn) => new MirFnStringifyer(this.ctx).fn(fn)) .toArray() .join("\n"); } public toArray(): Fn[] { return this.fns.values().toArray(); } } export class FnLowerer { private localIds = new Ids(); private locals = new IdMap(); private blockIds = new Ids(); private blocks = new IdMap(); private currentBlock?: Block; private reLocals = new IdMap(); private paramLocals = new IdMap(); private loopInfos = new IdMap(); public constructor( private ctx: Ctx, private re: Resols, private ch: Checker, private item: ast.Item, private kind: ast.FnItem, ) {} public lower(): Res { const entry = this.pushBlock(); const fnTy = this.ch.fnItemTy(this.item, this.kind); if (fnTy.kind.tag !== "fn") { throw new Error(); } const returnPlace = this.local(fnTy.kind.returnTy); const returnVal = this.lowerBlock(this.kind.body!); this.addStmt({ tag: "assign", place: { local: returnPlace, proj: [] }, rval: returnVal, }); this.setTer({ tag: "return" }); return Res.Ok({ label: this.ctx.identText(this.item.ident.id), locals: this.locals, blocks: this.blocks, entry: entry.id, paramLocals: this.paramLocals, astItem: this.item, astItemKind: this.kind, }); } private lowerBlock(block: ast.Block): RVal { for (const stmt of block.stmts) { this.lowerStmt(stmt); } return block.expr && this.lowerExpr(block.expr) || { tag: "use", operand: { tag: "const", val: { tag: "null" } }, }; } private lowerStmt(stmt: ast.Stmt) { const k = stmt.kind; switch (k.tag) { case "error": return { tag: "error" }; case "item": return todo(k.tag); case "let": return this.lowerLetStmt(stmt, k); case "return": return todo(k.tag); case "break": return this.lowerBreakStmt(stmt, k); case "continue": return todo(k.tag); case "assign": return this.lowerAssignStmt(stmt, k); case "expr": { const rval = this.lowerExpr(k.expr); // ignore the fuck out of the value const ty = this.ch.exprTy(k.expr); const local = this.local(ty); this.addStmt({ tag: "assign", place: { local, proj: [] }, rval, }); return; } } exhausted(k); } private lowerLetStmt(stmt: ast.Stmt, kind: ast.LetStmt) { const val = kind.expr && this.lowerExpr(kind.expr); this.allocatePat(kind.pat); if (val) { this.assignPatRVal(kind.pat, val); } } private allocatePat(pat: ast.Pat) { const k = pat.kind; switch (k.tag) { case "error": return; case "bind": { const ty = this.ch.patTy(pat); const local = k.mut ? this.localMut(ty, k.ident) : this.local(ty, k.ident); this.reLocals.set(pat.id, local); return; } case "path": return todo(); case "bool": return; case "tuple": case "struct": return todo(); } exhausted(k); } private assignPatRVal(pat: ast.Pat, rval: RVal) { const k = pat.kind; switch (k.tag) { case "error": return; case "bind": { const patLocal = this.reLocals.get(pat.id)!; this.addStmt({ tag: "assign", place: { local: patLocal, proj: [] }, rval, }); return; } case "path": return todo(); case "bool": return; case "tuple": case "struct": return todo(); } exhausted(k); } private lowerBreakStmt(stmt: ast.Stmt, kind: ast.BreakStmt) { this.ch.checkBreakStmt(stmt, kind); const re = this.re.loopRes(stmt.id); if (re.tag === "error") { return; } const info = this.loopInfos.get(re.expr.id)!; if (kind.expr) { const ty = this.ch.exprTy(kind.expr); info.resultLocal = info.resultLocal ?? this.local(ty); const rval = this.lowerExpr(kind.expr); this.addStmt({ tag: "assign", place: { local: info.resultLocal, proj: [] }, rval, }); } this.setTer({ tag: "goto", target: info.endBlock }); this.pushBlock(); } private lowerAssignStmt(stmt: ast.Stmt, kind: ast.AssignStmt) { this.ch.checkAssignStmt(stmt, kind); const rval = this.lowerExpr(kind.value); switch (kind.assignType) { case "=": return this.assignToExpr(kind.subject, rval, []); case "+=": case "-=": todo(); } } private assignToExpr(expr: ast.Expr, rval: RVal, proj: ProjElem[]) { const k = expr.kind; switch (k.tag) { case "path": { const re = this.re.exprRes(expr.id); if (re.kind.tag !== "local") { throw new Error("cannot assign. checker should catch"); } const patRe = this.re.patRes(re.kind.id); const local = this.reLocals.get(patRe.pat.id)!; this.addStmt({ tag: "assign", place: { local, proj: [] }, rval, }); return; } case "error": case "null": case "int": case "bool": case "str": case "group": case "array": case "repeat": case "struct": case "ref": case "deref": case "elem": case "field": case "index": case "call": case "unary": case "binary": case "block": case "if": case "match": case "loop": case "while": case "for": case "c_for": throw new Error("not assignable. checker should catch"); } exhausted(k); } private lowerExpr(expr: ast.Expr): RVal { const k = expr.kind; switch (k.tag) { case "error": return { tag: "error" }; case "path": return this.lowerPathExpr(expr, k); case "null": case "int": case "bool": return { tag: "use", operand: this.lowerExprToOperand(expr), }; case "str": case "group": case "array": case "repeat": return todo(k.tag); case "struct": return this.lowerStructExpr(expr, k); case "ref": case "deref": case "elem": case "field": case "index": return todo(k.tag); case "call": return this.lowerCallExpr(expr, k); case "unary": return todo(k.tag); case "binary": return this.lowerBinaryExpr(expr, k); case "block": return this.lowerBlock(k.block); case "if": return this.lowerIfExpr(expr, k); case "match": return this.lowerMatchExpr(expr, k); case "loop": return this.lowerLoopExpr(expr, k); case "while": return this.lowerWhileExpr(expr, k); case "for": return todo(k.tag); case "c_for": return this.lowerCForExpr(expr, k); } exhausted(k); } private lowerPathExpr(expr: ast.Expr, kind: ast.PathExpr): RVal { const re = this.re.exprRes(expr.id); switch (re.kind.tag) { case "error": return { tag: "error" }; case "enum": case "struct": return todo(); case "variant": { const ty = this.ch.enumItemTy(re.kind.item, re.kind.kind); const data = re.kind.variant.data; switch (data.kind.tag) { case "error": return { tag: "error" }; case "struct": throw new Error("should not check"); case "unit": return { tag: "adt", ty, fields: [], variant: re.kind.variant, }; case "tuple": return todo(); } return exhausted(data.kind); } case "field": return todo(); case "fn": case "local": return { tag: "use", operand: this.lowerPathExprToOperand(expr, kind), }; } exhausted(re.kind); } private lowerStructExpr(expr: ast.Expr, kind: ast.StructExpr): RVal { const ty = this.ch.exprTy(expr); let variant: ast.Variant | undefined = undefined; if (ty.kind.tag === "enum") { const res = this.re.pathRes(kind.path!.id); if (res.kind.tag !== "variant") { throw new Error(); } variant = res.kind.variant; } const fields = kind.fields .map((field) => this.lowerExprToOperand(field.expr)); return { tag: "adt", ty, fields, variant }; } private lowerCallExpr(expr: ast.Expr, kind: ast.CallExpr): RVal { if (this.callExprIsTupleStructCtor(kind)) { return this.lowerCallExprTupleStructCtor(expr, kind); } if (this.callExprIsTupleVariantCtor(kind)) { return this.lowerCallExprTupleVariantCtor(expr, kind); } const args = kind.args.map((arg) => this.lowerExprToOperand(arg)); const calleeTy = this.ch.exprTy(kind.expr); if (calleeTy.kind.tag !== "fn") { throw new Error(); } const builtinAttr = calleeTy.kind.item.attrs .find((attr) => attr.ident.text === "builtin"); if (builtinAttr) { if ( builtinAttr.args?.length !== 1 || builtinAttr.args[0].kind.tag !== "path" || builtinAttr.args[0].kind.path.segments.length !== 1 || !["Hello"].includes( builtinAttr.args[0].kind.path.segments[0].ident.text, ) ) { return { tag: "error" }; } const builtinId = builtinAttr.args[0].kind.path.segments[0].ident.text; return { tag: "builtin", builtinId, args }; } const func = this.lowerExprToOperand(kind.expr); return { tag: "call", func, args }; } private callExprIsTupleStructCtor(kind: ast.CallExpr): boolean { if (kind.expr.kind.tag !== "path") { return false; } const res = this.re.exprRes(kind.expr.id); return res.kind.tag === "struct"; } private lowerCallExprTupleStructCtor( expr: ast.Expr, kind: ast.CallExpr, ): RVal { const ty = this.ch.exprTy(expr); const fields = kind.args .map((arg) => this.lowerExprToOperand(arg)); return { tag: "adt", ty, fields }; } private callExprIsTupleVariantCtor(kind: ast.CallExpr): boolean { if (kind.expr.kind.tag !== "path") { return false; } const res = this.re.exprRes(kind.expr.id); return res.kind.tag === "variant" && res.kind.variant.data.kind.tag === "tuple"; } private lowerCallExprTupleVariantCtor( expr: ast.Expr, kind: ast.CallExpr, ): RVal { const res = this.re.exprRes(kind.expr.id); if (res.kind.tag !== "variant") { throw new Error(); } const variant = res.kind.variant; const ty = this.ch.exprTy(expr); const fields = kind.args .map((arg) => this.lowerExprToOperand(arg)); return { tag: "adt", ty, fields, variant }; } private lowerBinaryExpr(expr: ast.Expr, kind: ast.BinaryExpr): RVal { const left = this.lowerExprToOperand(kind.left); const right = this.lowerExprToOperand(kind.right); const binaryType = ((kind): BinaryType => { switch (kind.binaryType) { case "+": return "add"; case "-": return "sub"; case "*": return "mul"; case "/": return "div"; case "==": return "eq"; case "!=": return "ne"; case "<": return "lt"; case ">": return "lte"; case "<=": return "lte"; case ">=": return "gte"; case "or": return "or"; case "and": return "and"; } return todo(kind.binaryType); })(kind); return { tag: "binary", binaryType, left, right }; } private lowerIfExpr(expr: ast.Expr, kind: ast.IfExpr): RVal { const ty = this.ch.exprTy(expr); const discr = this.lowerExprToOperand(kind.cond); const condBlock = this.currentBlock!; if (kind.falsy) { const local = this.local(ty); const truthBlock = this.pushBlock(); const truthy = this.lowerExpr(kind.truthy); this.addStmt({ tag: "assign", place: { local, proj: [] }, rval: truthy, }); const falsyBlock = this.pushBlock(); const falsy = this.lowerExpr(kind.falsy); this.addStmt({ tag: "assign", place: { local, proj: [] }, rval: falsy, }); const exit = this.pushBlock(); this.setTer({ tag: "goto", target: exit.id }, truthBlock); this.setTer({ tag: "goto", target: exit.id }, falsyBlock); this.setTer({ tag: "switch", discr, targets: [{ value: 1, target: truthBlock.id }], otherwise: falsyBlock.id, }, condBlock); return { tag: "use", operand: this.copyOrMoveLocal(local, ty) }; } else { if (this.ch.exprTy(expr).kind.tag !== "null") { throw new Error(); } const truthBlock = this.pushBlock(); this.lowerExpr(kind.truthy); const exit = this.createBlock(); this.setTer({ tag: "goto", target: exit.id }); this.pushCreatedBlock(exit); this.setTer({ tag: "switch", discr, targets: [{ value: 1, target: truthBlock.id }], otherwise: exit.id, }, condBlock); return { tag: "use", operand: { tag: "const", val: { tag: "null" } }, }; } } private lowerMatchExpr(expr: ast.Expr, kind: ast.MatchExpr): RVal { if (kind.arms.length === 0) { return todo(); } const ty = this.ch.exprTy(expr); const dest = this.local(ty); const discr = this.lowerExpr(kind.expr); const exitBlock = this.createBlock(); for (const arm of kind.arms) { const exprBlock = this.createBlock(); const nextArmBlock = this.createBlock(); this.lowerMatchArmPattern( discr, arm.pat, exprBlock, nextArmBlock, ); this.pushCreatedBlock(exprBlock); this.lowerMatchArmPatternBindings(discr, arm.pat); const rval = this.lowerExpr(arm.expr); this.addStmt({ tag: "assign", place: { local: dest, proj: [] }, rval, }); this.setTer({ tag: "goto", target: exitBlock.id }); this.pushCreatedBlock(nextArmBlock); } this.setTer({ tag: "goto", target: exitBlock.id }); this.pushCreatedBlock(exitBlock); return { tag: "use", operand: this.copyOrMoveLocal(dest, ty) }; } private lowerMatchArmPattern( discr: RVal, pat: ast.Pat, truthyBlock: Block, falsyBlock: Block, ) { const discrOperand = this.lowerMatchArmPatternCondition(discr, pat); this.setTer({ tag: "switch", discr: discrOperand, targets: [{ value: 1, target: truthyBlock.id }], otherwise: falsyBlock.id, }); } private lowerMatchArmPatternCondition( discr: RVal, pat: ast.Pat, ): Operand { const k = pat.kind; switch (k.tag) { case "error": return { tag: "error" }; case "bind": { return { tag: "const", val: { tag: "int", value: 1 } }; } case "path": return todo(); case "bool": { const ty = this.ch.patTy(pat); if (ty.kind.tag !== "bool") { throw new Error(); } const discrLocal = this.local(ty); this.addStmt({ tag: "assign", place: { local: discrLocal, proj: [] }, rval: discr, }); const local = this.local(ty); this.addStmt({ tag: "assign", place: { local, proj: [] }, rval: { tag: "binary", binaryType: "eq", left: this.copyOrMoveLocal(discrLocal, ty), right: { tag: "const", val: { tag: "int", value: 1 } }, }, }); return this.copyOrMoveLocal(local, ty); } case "tuple": { const ty = this.ch.patTy(pat); if (ty.kind.tag === "struct") { const discrLocal = this.local(ty); this.addStmt({ tag: "assign", place: { local: discrLocal, proj: [] }, rval: discr, }); const condLocals: LocalId[] = []; for (const [fieldIdx, pat] of k.elems.entries()) { if (ty.kind.data.tag !== "tuple") { throw new Error(); } const elemTy = ty.kind.data.elems[fieldIdx].ty; const condOperand = this.lowerMatchArmPatternCondition({ tag: "use", operand: { tag: "move", place: { local: discrLocal, proj: [{ tag: "field", fieldIdx }], }, }, }, pat); const condLocal = this.local(Ty({ tag: "int" })); this.addStmt({ tag: "assign", place: { local: condLocal, proj: [] }, rval: { tag: "use", operand: condOperand }, }); condLocals.push(condLocal); } const condLocal = condLocals .slice(1) .reduce((rightLocal, leftLocal) => { const local = this.local(Ty({ tag: "int" })); this.addStmt({ tag: "assign", place: { local, proj: [] }, rval: { tag: "binary", binaryType: "and", left: { tag: "copy", place: { local: leftLocal, proj: [] }, }, right: { tag: "copy", place: { local: rightLocal, proj: [] }, }, }, }); return local; }, condLocals[0]); return this.copyOrMoveLocal(condLocal, Ty({ tag: "int" })); } return todo(); } case "struct": return todo(); } exhausted(k); } private lowerMatchArmPatternBindings( discr: RVal, pat: ast.Pat, ) { const k = pat.kind; switch (k.tag) { case "error": return; case "bind": { const ty = this.ch.patTy(pat); const local = this.local(ty); this.addStmt({ tag: "assign", place: { local, proj: [] }, rval: discr, }); return; } case "path": return; case "bool": return; case "tuple": { const ty = this.ch.patTy(pat); if (ty.kind.tag === "struct") { const discrLocal = this.local(ty); this.addStmt({ tag: "assign", place: { local: discrLocal, proj: [] }, rval: discr, }); for (const [fieldIdx, pat] of k.elems.entries()) { if (ty.kind.data.tag !== "tuple") { throw new Error(); } this.lowerMatchArmPatternBindings({ tag: "use", operand: { tag: "move", place: { local: discrLocal, proj: [{ tag: "field", fieldIdx }], }, }, }, pat); } return; } return todo(); } case "struct": return todo(); } exhausted(k); } private lowerLoopExpr(expr: ast.Expr, kind: ast.LoopExpr): RVal { const entryBlock = this.currentBlock!; const loopBlock = this.pushBlock(); const endBlock = this.createBlock(); const info = { loopBlock: loopBlock.id, endBlock: endBlock.id, resultLocal: undefined, }; this.loopInfos.set(expr.id, info); const rval = this.lowerExpr(kind.body); // ignore value; void rval; this.setTer({ tag: "goto", target: loopBlock.id }); this.setTer({ tag: "goto", target: loopBlock.id }, entryBlock); this.pushCreatedBlock(endBlock); if (info.resultLocal) { const ty = this.ch.exprTy(expr); return { tag: "use", operand: this.copyOrMoveLocal(info.resultLocal, ty), }; } else { return { tag: "use", operand: { tag: "const", val: { tag: "null" } }, }; } } private lowerWhileExpr(expr: ast.Expr, kind: ast.WhileExpr): RVal { const enterBlock = this.currentBlock!; const condBlock = this.pushBlock(); this.setTer({ tag: "goto", target: condBlock.id }, enterBlock); const condTy = this.ch.exprTy(kind.cond); const condLocal = this.localMut(condTy); const condVal = this.lowerExpr(kind.cond); this.addStmt({ tag: "assign", place: { local: condLocal, proj: [] }, rval: condVal, }); if (this.ch.exprTy(expr).kind.tag !== "null") { throw new Error(); } const bodyBlock = this.pushBlock(); const exitBlock = this.createBlock(); this.loopInfos.set(expr.id, { loopBlock: condBlock.id, endBlock: exitBlock.id, }); this.lowerExpr(kind.body); this.setTer({ tag: "goto", target: condBlock.id }); this.pushCreatedBlock(exitBlock); this.setTer({ tag: "switch", discr: this.copyOrMoveLocal(condLocal, condTy), targets: [{ value: 1, target: bodyBlock.id }], otherwise: exitBlock.id, }, condBlock); return { tag: "use", operand: { tag: "const", val: { tag: "null" } }, }; } private lowerCForExpr(expr: ast.Expr, kind: ast.CForExpr): RVal { kind.decl && this.lowerStmt(kind.decl); const enterBlock = this.currentBlock!; if (this.ch.exprTy(expr).kind.tag !== "null") { throw new Error(); } let loopBlock: Block; const exitBlock = this.createBlock(); if (kind.cond) { const condBlock = this.pushBlock(); this.setTer({ tag: "goto", target: condBlock.id }, enterBlock); const condTy = this.ch.exprTy(kind.cond); const condLocal = this.localMut(condTy); const condVal = this.lowerExpr(kind.cond); this.addStmt({ tag: "assign", place: { local: condLocal, proj: [] }, rval: condVal, }); const bodyBlock = this.pushBlock(); this.setTer({ tag: "switch", discr: this.copyOrMoveLocal(condLocal, condTy), targets: [{ value: 1, target: bodyBlock.id }], otherwise: exitBlock.id, }, condBlock); loopBlock = condBlock; this.loopInfos.set(expr.id, { loopBlock: condBlock.id, endBlock: exitBlock.id, }); } else { loopBlock = this.pushBlock(); this.setTer({ tag: "goto", target: loopBlock.id }, loopBlock); this.loopInfos.set(expr.id, { loopBlock: loopBlock.id, endBlock: exitBlock.id, }); } this.lowerExpr(kind.body); if (kind.incr) { this.lowerStmt(kind.incr); } this.setTer({ tag: "goto", target: loopBlock.id }); this.pushCreatedBlock(exitBlock); return { tag: "use", operand: { tag: "const", val: { tag: "null" } }, }; } private lowerExprToOperand(expr: ast.Expr): Operand { const k = expr.kind; switch (k.tag) { case "error": return { tag: "error" }; case "path": return this.lowerPathExprToOperand(expr, k); case "null": return { tag: "const", val: { tag: "null" } }; case "int": return { tag: "const", val: { tag: "int", value: k.value }, }; case "bool": return { tag: "const", val: { tag: "int", value: k.value ? 1 : 0 }, }; case "str": case "group": case "array": case "repeat": case "struct": case "ref": case "deref": case "elem": case "field": case "index": case "call": case "unary": case "binary": case "block": case "if": case "match": case "loop": case "while": case "for": case "c_for": { const ty = this.ch.exprTy(expr); const rval = this.lowerExpr(expr); const local = this.local(ty); this.addStmt({ tag: "assign", place: { local, proj: [] }, rval, }); return this.copyOrMoveLocal(local, ty); } } exhausted(k); } private lowerPathExprToOperand( expr: ast.Expr, kind: ast.PathExpr, ): Operand { const re = this.re.exprRes(expr.id); switch (re.kind.tag) { case "error": return { tag: "error" }; case "enum": return todo(); case "struct": { const data = re.kind.kind.data; switch (data.kind.tag) { case "error": return { tag: "error" }; case "struct": case "unit": case "tuple": return todo(data.kind.tag); } return exhausted(data.kind); } case "variant": { const data = re.kind.variant.data; switch (data.kind.tag) { case "error": return { tag: "error" }; case "struct": case "unit": case "tuple": return todo(data.kind.tag); } return exhausted(data.kind); } case "field": return todo(); case "local": { const patRes = this.re.patRes(re.kind.id); const ty = this.ch.exprTy(expr); let local: LocalId; if (this.reLocals.has(re.kind.id)) { local = this.reLocals.get(re.kind.id)!; } else { local = this.local(ty); this.reLocals.set(re.kind.id, local); } if (patRes.kind.tag === "fn_param") { this.paramLocals.set(local, patRes.kind.paramIdx); } return this.copyOrMoveLocal(local, ty); } case "fn": { const { item, kind } = re.kind; return { tag: "const", val: { tag: "fn", item, kind } }; } } exhausted(re.kind); } private copyOrMoveLocal(local: LocalId, ty: Ty): Operand { const isCopyable = (() => { switch (ty.kind.tag) { case "error": case "unknown": return false; case "null": case "int": case "bool": return true; case "fn": case "enum": case "struct": return false; } exhausted(ty.kind); })(); return { tag: isCopyable ? "copy" : "move", place: { local, proj: [] }, }; } private local(ty: Ty, ident?: ast.Ident): LocalId { const id = this.localIds.nextThenStep(); this.locals.set(id, { id, ty, mut: false, ident }); return id; } private localMut(ty: Ty, ident?: ast.Ident): LocalId { const id = this.localIds.nextThenStep(); this.locals.set(id, { id, ty, mut: true, ident }); return id; } private pushBlock(): Block { const id = this.blockIds.nextThenStep(); const block: Block = { id, stmts: [], terminator: { kind: { tag: "unset" } }, }; this.blocks.set(id, block); this.currentBlock = block; return block; } private createBlock(): Block { const id = this.blockIds.nextThenStep(); const block: Block = { id, stmts: [], terminator: { kind: { tag: "unset" } }, }; return block; } private pushCreatedBlock(block: Block) { this.blocks.set(block.id, block); this.currentBlock = block; } private setTer(kind: TerKind, block = this.currentBlock!) { block.terminator = { kind }; } private addStmt(kind: StmtKind, block = this.currentBlock!) { block.stmts.push({ kind }); } } function unpack(blocks: Block[], val: [Block[], RVal]): [Block[], RVal] { return [[...blocks, ...val[0]], val[1]]; }