diff --git a/compiler/ast.ts b/compiler/ast.ts index 46f030d..9c3a51d 100644 --- a/compiler/ast.ts +++ b/compiler/ast.ts @@ -63,6 +63,9 @@ export type ExprKind = sym: Sym; } | { type: "group"; expr: Expr } + | { type: "ref"; subject: Expr } + | { type: "ref_mut"; subject: Expr } + | { type: "deref"; subject: Expr } | { type: "array"; exprs: Expr[] } | { type: "struct"; fields: Field[] } | { type: "field"; subject: Expr; ident: string } @@ -117,6 +120,7 @@ export type Param = { id: number; index?: number; ident: string; + mut: boolean; etype?: EType; pos: Pos; sym?: Sym; @@ -157,7 +161,11 @@ export type ETypeKind = ident: string; sym: Sym; } - | { type: "array"; inner: EType } + | { type: "ref"; subject: EType } + | { type: "ref_mut"; subject: EType } + | { type: "ptr"; subject: EType } + | { type: "ptr_mut"; subject: EType } + | { type: "array"; subject: EType } | { type: "struct"; fields: Param[] } | { type: "type_of"; expr: Expr }; @@ -227,3 +235,7 @@ export class AnnoView { return anno; } } + +export function forceType(v: unknown): { type: string } { + return v as { type: string }; +} diff --git a/compiler/ast_visitor.ts b/compiler/ast_visitor.ts index ba6c8ac..578e23d 100644 --- a/compiler/ast_visitor.ts +++ b/compiler/ast_visitor.ts @@ -22,6 +22,9 @@ export interface AstVisitor { visitStringExpr?(expr: Expr, ...args: Args): VisitRes; visitIdentExpr?(expr: Expr, ...args: Args): VisitRes; visitGroupExpr?(expr: Expr, ...args: Args): VisitRes; + visitRefExpr?(expr: Expr, ...args: Args): VisitRes; + visitRefMutExpr?(expr: Expr, ...args: Args): VisitRes; + visitDerefExpr?(expr: Expr, ...args: Args): VisitRes; visitArrayExpr?(expr: Expr, ...args: Args): VisitRes; visitStructExpr?(expr: Expr, ...args: Args): VisitRes; visitFieldExpr?(expr: Expr, ...args: Args): VisitRes; @@ -50,6 +53,10 @@ export interface AstVisitor { visitStringEType?(etype: EType, ...args: Args): VisitRes; visitIdentEType?(etype: EType, ...args: Args): VisitRes; visitSymEType?(etype: EType, ...args: Args): VisitRes; + visitRefEType?(etype: EType, ...args: Args): VisitRes; + visitRefMutEType?(etype: EType, ...args: Args): VisitRes; + visitPtrEType?(etype: EType, ...args: Args): VisitRes; + visitPtrMutEType?(etype: EType, ...args: Args): VisitRes; visitArrayEType?(etype: EType, ...args: Args): VisitRes; visitStructEType?(etype: EType, ...args: Args): VisitRes; visitTypeOfEType?(etype: EType, ...args: Args): VisitRes; @@ -152,6 +159,18 @@ export function visitExpr( if (v.visitGroupExpr?.(expr, ...args) == "stop") return; visitExpr(expr.kind.expr, v, ...args); break; + case "ref": + if (v.visitRefExpr?.(expr, ...args) == "stop") return; + visitExpr(expr.kind.subject, v, ...args); + break; + case "ref_mut": + if (v.visitRefMutExpr?.(expr, ...args) == "stop") return; + visitExpr(expr.kind.subject, v, ...args); + break; + case "deref": + if (v.visitDerefExpr?.(expr, ...args) == "stop") return; + visitExpr(expr.kind.subject, v, ...args); + break; case "field": if (v.visitFieldExpr?.(expr, ...args) == "stop") return; visitExpr(expr.kind.subject, v, ...args); @@ -289,9 +308,25 @@ export function visitEType( case "sym": if (v.visitSymEType?.(etype, ...args) == "stop") return; break; + case "ref": + if (v.visitRefEType?.(etype, ...args) == "stop") return; + visitEType(etype.kind.subject, v, ...args); + break; + case "ref_mut": + if (v.visitRefMutEType?.(etype, ...args) == "stop") return; + visitEType(etype.kind.subject, v, ...args); + break; + case "ptr": + if (v.visitPtrEType?.(etype, ...args) == "stop") return; + visitEType(etype.kind.subject, v, ...args); + break; + case "ptr_mut": + if (v.visitPtrMutEType?.(etype, ...args) == "stop") return; + visitEType(etype.kind.subject, v, ...args); + break; case "array": if (v.visitArrayEType?.(etype, ...args) == "stop") return; - if (etype.kind.inner) visitEType(etype.kind.inner, v, ...args); + visitEType(etype.kind.subject, v, ...args); break; case "struct": if (v.visitStructEType?.(etype, ...args) == "stop") return; diff --git a/compiler/checker.ts b/compiler/checker.ts index 703644d..132eb25 100644 --- a/compiler/checker.ts +++ b/compiler/checker.ts @@ -1,4 +1,4 @@ -import { AnnoView, EType, Expr, Stmt, Sym } from "./ast.ts"; +import { AnnoView, EType, Expr, forceType, Stmt, Sym } from "./ast.ts"; import { printStackTrace, Reporter } from "./info.ts"; import { Pos } from "./token.ts"; import { @@ -286,7 +286,7 @@ export class Checker { } if ( subject.type == "array" && - !vtypesEqual(subject.inner, value) + !vtypesEqual(subject.subject, value) ) { this.report( `cannot assign incompatible type to array ` + @@ -348,6 +348,12 @@ export class Checker { return { type: "string" }; case "group": return this.checkExpr(expr.kind.expr); + case "ref": + return this.checkRefExpr(expr); + case "ref_mut": + return this.checkRefMutExpr(expr); + case "deref": + return this.checkDerefExpr(expr); case "array": throw new Error("should have been desugared"); case "struct": @@ -421,6 +427,89 @@ export class Checker { } } + public checkRefExpr(expr: Expr): VType { + if (expr.kind.type !== "ref") { + throw new Error(); + } + const subject = this.checkExpr(expr.kind.subject); + if (expr.kind.subject.kind.type === "sym") { + const sym = expr.kind.subject.kind.sym; + if (sym.type === "let" || sym.type === "fn_param") { + return { type: "ref", subject }; + } + this.report( + `taking reference to symbol type '${sym.type}' not supported`, + expr.pos, + ); + return { type: "error" }; + } + this.report( + `taking reference to expression type '${ + forceType(expr.kind.subject.kind).type + }' not supported`, + expr.pos, + ); + return { type: "error" }; + } + + public checkRefMutExpr(expr: Expr): VType { + if (expr.kind.type !== "ref_mut") { + throw new Error(); + } + const subject = this.checkExpr(expr.kind.subject); + if (expr.kind.subject.kind.type === "sym") { + const sym = expr.kind.subject.kind.sym; + if (sym.type === "let" || sym.type === "fn_param") { + if (!sym.param.mut) { + this.report( + `symbol '${sym.ident}' it not declared mutable`, + expr.pos, + ); + this.reporter.addNote({ + reporter: "checker", + msg: "symbol defined here", + pos: sym.param.pos, + }); + return { type: "error" }; + } + return { type: "ref_mut", subject }; + } + this.report( + `taking reference to symbol type '${sym.type}' not supported`, + expr.pos, + ); + return { type: "error" }; + } + this.report( + `taking mutable reference to expression type '${ + forceType(expr.kind.subject.kind).type + }' not supported`, + expr.pos, + ); + return { type: "error" }; + } + public checkDerefExpr(expr: Expr): VType { + if (expr.kind.type !== "deref") { + throw new Error(); + } + const subject = this.checkExpr(expr.kind.subject); + switch (subject.type) { + case "ref": + return subject.subject; + case "ref_mut": + return subject.subject; + case "ptr": + return subject.subject; + case "ptr_mut": + return subject.subject; + } + this.report( + `dereferenced type is neither a reference nor a pointer`, + expr.pos, + ); + return { type: "error" }; + } + public checkStructExpr(expr: Expr): VType { if (expr.kind.type !== "struct") { throw new Error(); @@ -472,7 +561,7 @@ export class Checker { return { type: "error" }; } if (subject.type === "array") { - return subject.inner; + return subject.subject; } return { type: "int" }; } @@ -653,7 +742,7 @@ export class Checker { return { a, b }; } if (a.type === "array" && b.type === "array") { - return this.reduceToSignificant(a.inner, b.inner); + return this.reduceToSignificant(a.subject, b.subject); } if (a.type === "generic" && b.type === "generic") { return { a, b }; @@ -670,8 +759,13 @@ export class Checker { case "int": case "bool": return false; + case "ref": + case "ref_mut": + case "ptr": + case "ptr_mut": + return this.vtypeContainsGeneric(vtype.subject); case "array": - return this.vtypeContainsGeneric(vtype.inner); + return this.vtypeContainsGeneric(vtype.subject); case "struct": return vtype.fields.some((field) => this.vtypeContainsGeneric(field.vtype) @@ -749,10 +843,14 @@ export class Checker { case "int": case "bool": return vtype; + case "ref": + case "ref_mut": + case "ptr": + case "ptr_mut": case "array": return { - type: "array", - inner: this.concretizeVType(vtype.inner, generics), + type: vtype.type, + subject: this.concretizeVType(vtype.subject, generics), }; case "struct": return { @@ -994,9 +1092,25 @@ export class Checker { this.report(`sym type '${etype.kind.sym.type}' used as type`, pos); return { type: "error" }; } + if (etype.kind.type === "ref") { + const subject = this.checkEType(etype.kind.subject); + return { type: "ref", subject }; + } + if (etype.kind.type === "ref_mut") { + const subject = this.checkEType(etype.kind.subject); + return { type: "ref", subject }; + } + if (etype.kind.type === "ptr") { + const subject = this.checkEType(etype.kind.subject); + return { type: "ptr", subject }; + } + if (etype.kind.type === "ptr_mut") { + const subject = this.checkEType(etype.kind.subject); + return { type: "ptr_mut", subject }; + } if (etype.kind.type === "array") { - const inner = this.checkEType(etype.kind.inner); - return { type: "array", inner }; + const subject = this.checkEType(etype.kind.subject); + return { type: "array", subject }; } if (etype.kind.type === "struct") { const noTypeTest = etype.kind.fields.reduce( diff --git a/compiler/compiler.ts b/compiler/compiler.ts index 22f1783..dd3a6f3 100644 --- a/compiler/compiler.ts +++ b/compiler/compiler.ts @@ -21,7 +21,8 @@ import { eliminateOnlyChildsBlocks, eliminateUnreachableBlocks, } from "./middle/elim_blocks.ts"; -import { eliminateTransientVals } from "./middle/elim_transient_vals.ts"; +import { checkBorrows } from "./middle/borrow_checker.ts"; +import { makeMoveCopyExplicit } from "./middle/explicit_move_copy.ts"; export type CompileResult = { program: number[]; @@ -53,44 +54,52 @@ export class Compiler { new Checker(this.reporter).check(ast); - const mir = lowerAst(ast); + //const mir = lowerAst(ast); + // + //console.log("Before optimizations:"); + //printMir(mir); - console.log("Before optimizations:"); - printMir(mir); - - const mirHistory = [mirOpCount(mir)]; - for (let i = 0; i < 1; ++i) { - eliminateUnusedLocals(mir, this.reporter, mirHistory.length === 1); - eliminateOnlyChildsBlocks(mir); - eliminateUnreachableBlocks(mir); - eliminateTransientVals(mir); - - const opCount = mirOpCount(mir); - const histOccurence = mirHistory - .filter((v) => v === opCount).length; - if (histOccurence >= 2) { - break; - } - mirHistory.push(opCount); - } - - console.log("After optimizations:"); - printMir(mir); + //const mirHistory = [mirOpCount(mir)]; + //for (let i = 0; i < 1; ++i) { + // eliminateUnusedLocals(mir, this.reporter, mirHistory.length === 1); + // eliminateOnlyChildsBlocks(mir); + // eliminateUnreachableBlocks(mir); + // eliminateTransientVals(mir); + // + // const opCount = mirOpCount(mir); + // const histOccurence = mirHistory + // .filter((v) => v === opCount).length; + // if (histOccurence >= 2) { + // break; + // } + // mirHistory.push(opCount); + //} + // + //console.log("After optimizations:"); + //printMir(mir); if (this.reporter.errorOccured()) { console.error("Errors occurred, stopping compilation."); Deno.exit(1); } - const { monoFns, callMap } = new Monomorphizer(ast).monomorphize(); + const mir = lowerAst(ast); - const lastPos = await lastPosInTextFile(this.startFilePath); + makeMoveCopyExplicit(mir); + checkBorrows(mir, this.reporter); - const lowerer = new Lowerer(monoFns, callMap, lastPos); - const { program, fnNames } = lowerer.lower(); - //lowerer.printProgram(); + printMir(mir); - return { program, fnNames }; + //const { monoFns, callMap } = new Monomorphizer(ast).monomorphize(); + // + //const lastPos = await lastPosInTextFile(this.startFilePath); + // + //const lowerer = new Lowerer(monoFns, callMap, lastPos); + //const { program, fnNames } = lowerer.lower(); + ////lowerer.printProgram(); + // + //return { program, fnNames }; + return { program: [], fnNames: {} }; } } diff --git a/compiler/desugar/array_literal.ts b/compiler/desugar/array_literal.ts index 70cd488..c8bee61 100644 --- a/compiler/desugar/array_literal.ts +++ b/compiler/desugar/array_literal.ts @@ -51,6 +51,7 @@ export class ArrayLiteralDesugarer implements AstVisitor { type: "let", param: this.astCreator.param({ ident: "::value", + mut: true, pos: npos, }), value: Expr({ diff --git a/compiler/desugar/special_loop.ts b/compiler/desugar/special_loop.ts index f32a776..3ef127f 100644 --- a/compiler/desugar/special_loop.ts +++ b/compiler/desugar/special_loop.ts @@ -72,6 +72,7 @@ export class SpecialLoopDesugarer implements AstVisitor { type: "let", param: this.astCreator.param({ ident: "::values", + mut: true, pos: npos, }), value: expr.kind.value, @@ -80,6 +81,7 @@ export class SpecialLoopDesugarer implements AstVisitor { type: "let", param: this.astCreator.param({ ident: "::length", + mut: false, pos: npos, }), value: Expr({ @@ -104,6 +106,7 @@ export class SpecialLoopDesugarer implements AstVisitor { type: "let", param: this.astCreator.param({ ident: "::index", + mut: true, pos: npos, }), value: Expr({ type: "int", value: 0 }), diff --git a/compiler/desugar/struct_literal.ts b/compiler/desugar/struct_literal.ts index cec22d5..e7a24bf 100644 --- a/compiler/desugar/struct_literal.ts +++ b/compiler/desugar/struct_literal.ts @@ -54,6 +54,7 @@ export class StructLiteralDesugarer implements AstVisitor { type: "let", param: this.astCreator.param({ ident: "::value", + mut: true, pos: npos, }), value: Expr({ diff --git a/compiler/info.ts b/compiler/info.ts index bc6d7ff..df1e6d1 100644 --- a/compiler/info.ts +++ b/compiler/info.ts @@ -31,7 +31,7 @@ export class Reporter { private printReport({ reporter, type, pos, msg }: Report) { console.error( `${reporter} ${type}: ${msg}${ - pos ? `\n at ${this.filePath}:${pos.line}:${pos.col}` : "" + pos ? `\n at ${this.filePath}:${pos.line}:${pos.col}` : "" }`, ); } diff --git a/compiler/lexer.ts b/compiler/lexer.ts index a962341..f5dd6fd 100644 --- a/compiler/lexer.ts +++ b/compiler/lexer.ts @@ -36,6 +36,7 @@ export class Lexer { "break", "return", "let", + "mut", "fn", "loop", "if", @@ -128,7 +129,7 @@ export class Lexer { this.step(); return { ...this.token("string", pos), stringValue: value }; } - if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]>(); + + private assignedTo = false; + private moved = false; + private borrowed = false; + private borrowedMut = false; + + private movedPos?: Pos; + private borrowedPos?: Pos; + + public constructor( + private local: Local, + private fn: Fn, + private cfg: Cfg, + private reporter: Reporter, + ) {} + + public check() { + this.checkBlock(this.cfg.entry()); + } + + private checkBlock(block: Block) { + if (this.visitedBlocks.has(block.id)) { + return; + } + this.visitedBlocks.add(block.id); + for (const op of block.ops) { + const ok = op.kind; + switch (ok.type) { + case "error": + break; + case "assign": + this.markDst(ok.dst); + this.markSrc(ok.src); + break; + case "ref": + case "ptr": + this.markDst(ok.dst); + this.markBorrow(ok.src); + break; + case "ref_mut": + case "ptr_mut": + this.markDst(ok.dst); + this.markBorrowMut(ok.src); + break; + case "deref": + this.markDst(ok.dst); + this.markSrc(ok.src); + break; + case "assign_deref": + this.markSrc(ok.subject); + this.markSrc(ok.src); + break; + case "field": + this.markDst(ok.dst); + this.markSrc(ok.subject); + break; + case "assign_field": + this.markSrc(ok.subject); + this.markSrc(ok.src); + break; + case "index": + this.markDst(ok.dst); + this.markSrc(ok.subject); + this.markSrc(ok.index); + break; + case "assign_index": + this.markSrc(ok.subject); + this.markSrc(ok.index); + this.markSrc(ok.src); + break; + case "call_val": + this.markDst(ok.dst); + this.markSrc(ok.subject); + for (const arg of ok.args) { + this.markSrc(arg); + } + break; + case "binary": + this.markDst(ok.dst); + this.markSrc(ok.left); + this.markSrc(ok.right); + break; + } + } + const tk = block.ter.kind; + switch (tk.type) { + case "error": + break; + case "return": + break; + case "jump": + break; + case "if": + this.markSrc(tk.cond); + break; + } + for (const child of this.cfg.children(block)) { + this.checkBlock(child); + } + } + + private markDst(localId: LocalId) { + if (localId !== this.local.id) { + return; + } + if (!this.assignedTo) { + this.assignedTo = true; + return; + } + if (!this.local.mut) { + this.reportReassignToNonMut(); + return; + } + } + + private markBorrow(localId: LocalId) { + if (localId !== this.local.id) { + return; + } + + if (!this.assignedTo) { + this.assignedTo = true; + return; + } + } + + private markBorrowMut(localId: LocalId) { + if (localId !== this.local.id) { + return; + } + + if (!this.assignedTo) { + this.assignedTo = true; + return; + } + } + + private markSrc(src: RValue) { + if (src.type === "local") { + throw new Error("should be 'copy' or 'move'"); + } + if ( + (src.type !== "copy" && src.type !== "move") || + src.id !== this.local.id + ) { + return; + } + if (src.type === "move") { + if (this.moved) { + this.reportUseMoved(); + return; + } + if (this.borrowed) { + this.reportUseBorrowed(); + return; + } + this.moved = true; + } + } + + private reportReassignToNonMut() { + const ident = this.local.sym!.ident; + this.reporter.reportError({ + reporter: "borrow checker", + msg: `cannot re-assign to '${ident}' as it was not declared mutable`, + pos: this.local.sym!.pos!, + }); + this.reporter.addNote({ + reporter: "borrow checker", + msg: `declared here`, + pos: this.local.sym!.pos!, + }); + } + + private reportUseMoved() { + const ident = this.local.sym!.ident; + this.reporter.reportError({ + reporter: "borrow checker", + msg: `cannot use '${ident}' as it has been moved`, + pos: this.local.sym!.pos!, + }); + if (this.movedPos) { + this.reporter.addNote({ + reporter: "borrow checker", + msg: `moved here`, + pos: this.movedPos, + }); + } + } + + private reportUseBorrowed() { + const ident = this.local.sym!.ident; + this.reporter.reportError({ + reporter: "borrow checker", + msg: `cannot use '${ident}' as it has been borrowed`, + pos: this.local.sym!.pos!, + }); + if (this.borrowedPos) { + this.reporter.addNote({ + reporter: "borrow checker", + msg: `borrowed here`, + pos: this.movedPos, + }); + } + } +} diff --git a/compiler/middle/cfg.ts b/compiler/middle/cfg.ts index 377b3bc..3f72dfc 100644 --- a/compiler/middle/cfg.ts +++ b/compiler/middle/cfg.ts @@ -1,4 +1,4 @@ -import { Block, BlockId, Fn, Mir } from "./mir.ts"; +import { Block, BlockId, Fn } from "./mir.ts"; export function createCfg(fn: Fn): Cfg { return new CfgBuilder(fn).build(); @@ -7,10 +7,14 @@ export function createCfg(fn: Fn): Cfg { export class Cfg { public constructor( private graph: Map, - private entry: BlockId, + private entry_: BlockId, private exit: BlockId, ) {} + public entry(): Block { + return this.graph.get(this.entry_)!.block; + } + public parents(block: Block): Block[] { return this.graph .get(block.id)!.parents diff --git a/compiler/middle/elim_transient_locals.ts b/compiler/middle/elim_transient_locals.ts deleted file mode 100644 index d62c3da..0000000 --- a/compiler/middle/elim_transient_locals.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { FnStmtKind } from "../ast.ts"; -import { - Block, - Fn, - Local, - LocalId, - Mir, - replaceBlockSrcs, - RValue, -} from "./mir.ts"; - -export function eliminateTransientLocals(mir: Mir) { - for (const fn of mir.fns) { - const otherLocals = fn.locals - .slice(1 + (fn.stmt.kind as FnStmtKind).params.length) - .map((local) => local.id); - - for (const block of fn.blocks) { - new EliminateTransientLocalsBlockPass(block, otherLocals) - .pass(); - } - } -} - -type Candidate = { - dst: LocalId; - src: LocalId; -}; - -class EliminateTransientLocalsBlockPass { - private candidates: Candidate[] = []; - - public constructor( - private block: Block, - private readonly locals: LocalId[], - ) { - } - - public pass() { - this.findCandidatesInBlock(this.block); - - this.candidates = this.candidates - .filter((cand) => this.locals.includes(cand.dst)); - - this.eliminateCandidatesInBlock(this.block); - } - - private eliminateCandidatesInBlock(block: Block) { - replaceBlockSrcs(block, (src) => this.replaceMaybe(src)); - } - - private replaceMaybe(src: RValue): RValue { - if (src.type !== "local") { - return src; - } - const candidate = this.candidates - .find((cand) => cand.dst === src.id)?.src; - return candidate !== undefined ? { type: "local", id: candidate } : src; - } - - private findCandidatesInBlock(block: Block) { - for (const op of block.ops) { - const ok = op.kind; - switch (ok.type) { - case "error": - break; - case "assign": - this.markDst(ok.dst, ok.src); - break; - case "assign_error": - case "assign_null": - case "assign_bool": - case "assign_int": - case "assign_string": - case "assign_fn": - case "field": - case "assign_field": - case "index": - case "assign_index": - case "call_val": - case "binary": - break; - default: - throw new Error(); - } - } - const tk = block.ter.kind; - switch (tk.type) { - case "error": - break; - case "return": - break; - case "jump": - break; - case "if": - break; - } - } - - private markDst(dst: LocalId, src: RValue) { - if (src.type !== "local") { - return; - } - - this.candidates = this.candidates - .filter((cand) => cand.dst !== dst); - this.candidates = this.candidates - .filter((cand) => cand.src !== dst); - this.candidates.push({ dst, src: src.id }); - } -} diff --git a/compiler/middle/elim_transient_vals.ts b/compiler/middle/elim_transient_vals.ts deleted file mode 100644 index 30c9eeb..0000000 --- a/compiler/middle/elim_transient_vals.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { Mir, Op, RValue, visitBlockSrcs } from "./mir.ts"; - -export function eliminateTransientVals(mir: Mir) { - for (const fn of mir.fns) { - for (const block of fn.blocks) { - const cands: { src: RValue; consumer: Op; definition: Op }[] = []; - - visitBlockSrcs(block, (src, op, i) => { - if (src.type !== "local") { - return; - } - const found = block.ops.find((op, fi) => - op.kind.type === "assign" && - op.kind.dst === src.id && - fi < i! - ); - if (!found) { - return; - } - cands.push({ src, consumer: op!, definition: found! }); - }); - - //console.log(cands); - - for (const { src: oldsrc, consumer, definition } of cands) { - if (oldsrc.type !== "local") { - throw new Error(); - } - if (definition.kind.type !== "assign") { - throw new Error(); - } - const src = definition.kind.src; - - const k = consumer.kind; - switch (k.type) { - case "error": - break; - case "assign": - k.src = src; - break; - case "field": - if (same(k.subject, oldsrc)) { - k.subject = src; - } - break; - case "assign_field": - if (same(k.subject, oldsrc)) { - k.subject = src; - } - if (same(k.src, oldsrc)) { - k.src = src; - } - break; - case "index": - if (same(k.subject, oldsrc)) { - k.subject = src; - } - if (same(k.index, oldsrc)) { - k.index = src; - } - break; - case "assign_index": - if (same(k.subject, oldsrc)) { - k.subject = src; - } - if (same(k.index, oldsrc)) { - k.index = src; - } - if (same(k.src, oldsrc)) { - k.src = src; - } - break; - case "call_val": - if (same(k.subject, oldsrc)) { - k.subject = src; - } - for (let i = 0; i < k.args.length; ++i) { - if (same(k.args[i], oldsrc)) { - k.args[i] = src; - } - } - break; - case "binary": - if (same(k.left, oldsrc)) { - k.left = src; - } - if (same(k.right, oldsrc)) { - k.right = src; - } - break; - } - } - } - } -} - -function same(a: RValue, b: RValue): boolean { - return a.type === "local" && a.type === b.type && a.id === b.id; -} diff --git a/compiler/middle/explicit_move_copy.ts b/compiler/middle/explicit_move_copy.ts new file mode 100644 index 0000000..ea9984c --- /dev/null +++ b/compiler/middle/explicit_move_copy.ts @@ -0,0 +1,58 @@ +import { VType } from "../vtype.ts"; +import { Fn, Local, Mir, replaceBlockSrcs, RValue } from "./mir.ts"; + +export function makeMoveCopyExplicit(mir: Mir) { + for (const fn of mir.fns) { + for (const local of fn.locals) { + new LocalExpliciter(fn, local).pass(); + } + } +} + +class LocalExpliciter { + private copyable: boolean; + + public constructor(private fn: Fn, private local: Local) { + this.copyable = copyableIsType(local.vtype); + } + + public pass() { + for (const block of this.fn.blocks) { + replaceBlockSrcs(block, (src) => this.explicitSrc(src)); + } + } + + private explicitSrc(src: RValue): RValue { + if (src.type !== "local") { + return src; + } + return this.copyable + ? { type: "copy", id: src.id } + : { type: "move", id: src.id }; + } +} + +function copyableIsType(vtype: VType): boolean { + switch (vtype.type) { + case "error": + case "unknown": + throw new Error(); + case "null": + case "int": + case "bool": + case "string": + case "ref": + case "ref_mut": + case "ptr": + case "ptr_mut": + return true; + case "array": + case "struct": + case "fn": + return false; + case "generic": + return false; + case "generic_spec": + throw new Error(); + } +} diff --git a/compiler/middle/lower_ast.ts b/compiler/middle/lower_ast.ts index edbe27b..8c1b408 100644 --- a/compiler/middle/lower_ast.ts +++ b/compiler/middle/lower_ast.ts @@ -103,7 +103,7 @@ class FnAstLowerer { const val = this.lowerExpr(stmt.kind.expr); this.addOp({ type: "assign", dst, src: local(val) }); } else { - this.addOp({ type: "assign_null", dst }); + this.addOp({ type: "assign", dst, src: { type: "null" } }); } this.setTer({ type: "jump", target: block }); this.pushBlock(); @@ -227,6 +227,24 @@ class FnAstLowerer { return this.lowerSymExpr(expr); case "group": return this.lowerExpr(expr.kind.expr); + case "ref": { + const src = this.lowerExpr(expr.kind.subject); + const dst = this.locals.alloc(expr.vtype!); + this.addOp({ type: "ref", dst, src }); + return dst; + } + case "ref_mut": { + const src = this.lowerExpr(expr.kind.subject); + const dst = this.locals.alloc(expr.vtype!); + this.addOp({ type: "ref_mut", dst, src }); + return dst; + } + case "deref": { + const src = local(this.lowerExpr(expr.kind.subject)); + const dst = this.locals.alloc(expr.kind.subject.vtype!); + this.addOp({ type: "deref", dst, src }); + return dst; + } case "array": throw new Error("incomplete desugar"); case "struct": @@ -274,7 +292,7 @@ class FnAstLowerer { throw new Error(); } const dst = this.locals.alloc(sym.stmt.kind.vtype!); - this.addOp({ type: "assign_fn", dst, stmt }); + this.addOp({ type: "assign", dst, src: { type: "fn", stmt } }); return dst; } case "fn_param": { @@ -320,7 +338,7 @@ class FnAstLowerer { const dstVType = ((): VType => { const outer = expr.kind.subject.vtype!; if (outer.type === "array") { - return outer.inner; + return outer.subject; } if (outer.type === "string") { return { type: "int" }; @@ -442,7 +460,7 @@ class FnAstLowerer { return this.lowerExpr(expr.kind.expr); } else { const local = this.locals.alloc({ type: "null" }); - this.addOp({ type: "assign_null", dst: local }); + this.addOp({ type: "assign", dst: local, src: { type: "null" } }); return local; } } diff --git a/compiler/middle/mir.ts b/compiler/middle/mir.ts index d2f674a..128e384 100644 --- a/compiler/middle/mir.ts +++ b/compiler/middle/mir.ts @@ -41,6 +41,13 @@ type R = RValue; export type OpKind = | { type: "error" } | { type: "assign"; dst: L; src: R } + | { type: "ref"; dst: L; src: L } + | { type: "ref_mut"; dst: L; src: L } + | { type: "ptr"; dst: L; src: L } + | { type: "ptr_mut"; dst: L; src: L } + | { type: "drop"; src: L } + | { type: "deref"; dst: L; src: R } + | { type: "assign_deref"; subject: R; src: R } | { type: "field"; dst: L; subject: R; ident: string } | { type: "assign_field"; subject: R; ident: string; src: R } | { type: "index"; dst: L; subject: R; index: R } @@ -61,6 +68,8 @@ export type TerKind = export type RValue = | { type: "error" } | { type: "local"; id: BlockId } + | { type: "copy"; id: BlockId } + | { type: "move"; id: BlockId } | { type: "null" } | { type: "bool"; val: boolean } | { type: "int"; val: number } @@ -77,12 +86,18 @@ export function visitBlockDsts( case "error": break; case "assign": + case "ref": + case "ref_mut": + case "ptr": + case "ptr_mut": + case "deref": case "field": case "index": case "call_val": case "binary": visit(ok.dst, i); break; + case "assign_deref": case "assign_field": case "assign_index": break; @@ -104,6 +119,19 @@ export function replaceBlockSrcs( case "assign": ok.src = replace(ok.src); break; + case "ref": + case "ref_mut": + case "ptr": + case "ptr_mut": + case "drop": + break; + case "deref": + ok.src = replace(ok.src); + break; + case "assign_deref": + ok.subject = replace(ok.subject); + ok.src = replace(ok.src); + break; case "field": ok.subject = replace(ok.subject); break; @@ -158,6 +186,19 @@ export function visitBlockSrcs( case "assign": visitor(ok.src, op, i); break; + case "ref": + case "ref_mut": + case "ptr": + case "ptr_mut": + case "drop": + break; + case "deref": + visitor(ok.src, op, i); + break; + case "assign_deref": + visitor(ok.src, op, i); + visitor(ok.subject, op, i); + break; case "field": visitor(ok.subject, op, i); break; @@ -254,6 +295,27 @@ export function printMir(mir: Mir) { case "assign": l(`_${k.dst} = ${r(k.src)};`); break; + case "ref": + l(`_${k.dst} = &_${k.src};`); + break; + case "ref_mut": + l(`_${k.dst} = &mut _${k.src};`); + break; + case "ptr": + l(`_${k.dst} = *_${k.src};`); + break; + case "ptr_mut": + l(`_${k.dst} = *mut _${k.src};`); + break; + case "drop": + l(`drop _${k.src};`); + break; + case "deref": + l(`_${k.dst} = *${r(k.src)};`); + break; + case "assign_deref": + l(`*${r(k.subject)} = ${r(k.src)};`); + break; case "field": l(`_${k.dst} = ${r(k.subject)}.${k.ident};`); break; @@ -277,6 +339,8 @@ export function printMir(mir: Mir) { };`); break; } + default: + throw new Error(); } } const tk = block.ter.kind; @@ -295,6 +359,8 @@ export function printMir(mir: Mir) { r(tk.cond) }, true: bb${tk.truthy}, false: bb${tk.falsy};`); break; + default: + throw new Error(); } console.log(" }"); } @@ -308,6 +374,10 @@ export function rvalueToString(rvalue: RValue): string { return ``; case "local": return `_${rvalue.id}`; + case "copy": + return `copy _${rvalue.id}`; + case "move": + return `move _${rvalue.id}`; case "null": return "null"; case "bool": diff --git a/compiler/mono.ts b/compiler/mono.ts index 45a1cb3..d7abd63 100644 --- a/compiler/mono.ts +++ b/compiler/mono.ts @@ -163,8 +163,16 @@ function vtypeNameGenPart(vtype: VType): string { case "null": case "unknown": return vtype.type; + case "ref": + return `&${vtypeNameGenPart(vtype.subject)}`; + case "ref_mut": + return `&mut ${vtypeNameGenPart(vtype.subject)}`; + case "ptr": + return `*${vtypeNameGenPart(vtype.subject)}`; + case "ptr_mut": + return `*mut ${vtypeNameGenPart(vtype.subject)}`; case "array": - return `[${vtypeNameGenPart(vtype.inner)}]`; + return `[${vtypeNameGenPart(vtype.subject)}]`; case "struct": { const fields = vtype.fields .map((field) => diff --git a/compiler/parser.ts b/compiler/parser.ts index a423b51..4c02eb4 100644 --- a/compiler/parser.ts +++ b/compiler/parser.ts @@ -388,7 +388,12 @@ export class Parser { private parseParam(index?: number): Res { const pos = this.pos(); - if (this.test("ident")) { + if (this.test("ident") || this.test("mut")) { + let mut = false; + if (this.test("mut")) { + mut = true; + this.step(); + } const ident = this.current().identValue!; this.step(); if (this.test(":")) { @@ -396,12 +401,18 @@ export class Parser { const etype = this.parseEType(); return { ok: true, - value: this.astCreator.param({ index, ident, etype, pos }), + value: this.astCreator.param({ + index, + ident, + mut, + etype, + pos, + }), }; } return { ok: true, - value: this.astCreator.param({ index, ident, pos }), + value: this.astCreator.param({ index, ident, mut, pos }), }; } this.report("expected param"); @@ -779,6 +790,21 @@ export class Parser { const subject = this.parsePrefix(); return this.expr({ type: "unary", unaryType, subject }, pos); } + if (this.test("&")) { + this.step(); + let type: "ref" | "ref_mut" = "ref"; + if (this.test("mut")) { + this.step(); + type = "ref_mut"; + } + const subject = this.parsePrefix(); + return this.expr({ type, subject }, pos); + } + if (this.test("*")) { + this.step(); + const subject = this.parsePrefix(); + return this.expr({ type: "deref", subject }, pos); + } return this.parsePostfix(); } @@ -955,13 +981,13 @@ export class Parser { } if (this.test("[")) { this.step(); - const inner = this.parseEType(); + const subject = this.parseEType(); if (!this.test("]")) { this.report("expected ']'", pos); return this.etype({ type: "error" }, pos); } this.step(); - return this.etype({ type: "array", inner }, pos); + return this.etype({ type: "array", subject }, pos); } if (this.test("struct")) { this.step(); @@ -972,6 +998,26 @@ export class Parser { const fields = this.parseETypeStructFields(); return this.etype({ type: "struct", fields }, pos); } + if (this.test("&")) { + this.step(); + let type: "ref" | "ref_mut" = "ref"; + if (this.test("mut")) { + this.step(); + type = "ref_mut"; + } + const subject = this.parseEType(); + return this.etype({ type, subject }, pos); + } + if (this.test("*")) { + this.step(); + let type: "ptr" | "ptr_mut" = "ptr"; + if (this.test("mut")) { + this.step(); + type = "ptr_mut"; + } + const subject = this.parseEType(); + return this.etype({ type, subject }, pos); + } this.report("expected type"); return this.etype({ type: "error" }, pos); } diff --git a/compiler/resolver.ts b/compiler/resolver.ts index b8afa4c..55723d9 100644 --- a/compiler/resolver.ts +++ b/compiler/resolver.ts @@ -107,7 +107,7 @@ export class Resolver implements AstVisitor<[Syms]> { return "stop"; } - visitTypeAliasStmt(stmt: Stmt, syms: Syms): VisitRes { + visitTypeAliasStmt(stmt: Stmt, _syms: Syms): VisitRes { if (stmt.kind.type !== "type_alias") { throw new Error("expected type_alias statement"); } diff --git a/compiler/vtype.ts b/compiler/vtype.ts index 48a8d8e..24946e8 100644 --- a/compiler/vtype.ts +++ b/compiler/vtype.ts @@ -5,7 +5,11 @@ export type VType = | { type: "int" } | { type: "string" } | { type: "bool" } - | { type: "array"; inner: VType } + | { type: "ref"; subject: VType } + | { type: "ref_mut"; subject: VType } + | { type: "ptr"; subject: VType } + | { type: "ptr_mut"; subject: VType } + | { type: "array"; subject: VType } | { type: "struct"; fields: VTypeParam[] } | { type: "fn"; @@ -45,8 +49,20 @@ export function vtypesEqual( ) { return true; } + if (a.type === "ref" && b.type === "ref") { + return vtypesEqual(a.subject, b.subject, generics); + } + if (a.type === "ref_mut" && b.type === "ref_mut") { + return vtypesEqual(a.subject, b.subject, generics); + } + if (a.type === "ptr" && b.type === "ptr") { + return vtypesEqual(a.subject, b.subject, generics); + } + if (a.type === "ptr_mut" && b.type === "ptr_mut") { + return vtypesEqual(a.subject, b.subject, generics); + } if (a.type === "array" && b.type === "array") { - return vtypesEqual(a.inner, b.inner, generics); + return vtypesEqual(a.subject, b.subject, generics); } if (a.type === "struct" && b.type === "struct") { if (a.fields.length !== b.fields.length) { @@ -117,8 +133,20 @@ export function vtypeToString(vtype: VType): string { ) { return vtype.type; } + if (vtype.type === "ref") { + return `&${vtypeToString(vtype.subject)}`; + } + if (vtype.type === "ref_mut") { + return `&mut ${vtypeToString(vtype.subject)}`; + } + if (vtype.type === "ptr") { + return `*${vtypeToString(vtype.subject)}`; + } + if (vtype.type === "ptr_mut") { + return `*mut ${vtypeToString(vtype.subject)}`; + } if (vtype.type === "array") { - return `[${vtypeToString(vtype.inner)}]`; + return `[${vtypeToString(vtype.subject)}]`; } if (vtype.type === "struct") { const fields = vtype.fields diff --git a/examples/example_2.slg b/examples/example_2.slg index 45134ae..1dac50e 100644 --- a/examples/example_2.slg +++ b/examples/example_2.slg @@ -4,7 +4,14 @@ fn add(a: int, b: int) -> int { fn main() -> int { let result = 0; - let i = 0; + + let a = 0; + let b = a; + let c = b; + let d = c; + + + let i = c; loop { if i >= 10 { break; diff --git a/examples/refs.slg b/examples/refs.slg new file mode 100644 index 0000000..8616137 --- /dev/null +++ b/examples/refs.slg @@ -0,0 +1,7 @@ +// + +fn main() { + let a = 5; + let b: &int = &a; +} +