add borrow checker

This commit is contained in:
sfja 2025-01-17 07:44:53 +01:00
parent a7349890d0
commit 5d6b1abefc
22 changed files with 721 additions and 269 deletions

View File

@ -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 };
}

View File

@ -22,6 +22,9 @@ export interface AstVisitor<Args extends unknown[] = []> {
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<Args extends unknown[] = []> {
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<Args extends unknown[] = []>(
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<Args extends unknown[] = []>(
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;

View File

@ -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(

View File

@ -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: {} };
}
}

View File

@ -51,6 +51,7 @@ export class ArrayLiteralDesugarer implements AstVisitor {
type: "let",
param: this.astCreator.param({
ident: "::value",
mut: true,
pos: npos,
}),
value: Expr({

View File

@ -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 }),

View File

@ -54,6 +54,7 @@ export class StructLiteralDesugarer implements AstVisitor {
type: "let",
param: this.astCreator.param({
ident: "::value",
mut: true,
pos: npos,
}),
value: Expr({

View File

@ -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}` : ""
}`,
);
}

View File

@ -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(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#]/)) {
if (this.test(/[\+\{\};=\-\*\(\)\.,:;\[\]><!0#&]/)) {
const first = this.current();
this.step();
if (first === "=" && !this.done() && this.test("=")) {

View File

@ -0,0 +1,240 @@
import { Reporter } from "../info.ts";
import { Pos } from "../token.ts";
import { createCfg } from "./cfg.ts";
import { Cfg } from "./cfg.ts";
import { Block, BlockId, Fn, Local, LocalId, Mir, RValue } from "./mir.ts";
export function checkBorrows(
mir: Mir,
reporter: Reporter,
) {
for (const fn of mir.fns) {
new BorrowCheckerFnPass(fn, reporter).pass();
}
}
class BorrowCheckerFnPass {
private cfg: Cfg;
public constructor(
private fn: Fn,
private reporter: Reporter,
) {
this.cfg = createCfg(this.fn);
}
public pass() {
for (const local of this.fn.locals) {
new LocalChecker(local, this.fn, this.cfg, this.reporter).check();
}
}
}
class LocalChecker {
private visitedBlocks = new Set<BlockId>();
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,
});
}
}
}

View File

@ -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<BlockId, CfgNode>,
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

View File

@ -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 });
}
}

View File

@ -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;
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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 `<error>`;
case "local":
return `_${rvalue.id}`;
case "copy":
return `copy _${rvalue.id}`;
case "move":
return `move _${rvalue.id}`;
case "null":
return "null";
case "bool":

View File

@ -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) =>

View File

@ -388,7 +388,12 @@ export class Parser {
private parseParam(index?: number): Res<Param> {
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);
}

View File

@ -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");
}

View File

@ -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

View File

@ -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;

7
examples/refs.slg Normal file
View File

@ -0,0 +1,7 @@
//
fn main() {
let a = 5;
let b: &int = &a;
}