@ -0,0 +1,25 @@ | |||
export const intersect = <A>(xs: Set<A>, ys: Set<A>): Set<A> => { | |||
const out: Set<A> = new Set() | |||
xs.forEach(x => { | |||
if (ys.has(x)) { | |||
out.add(x) | |||
} | |||
}) | |||
return out | |||
} | |||
export const setEquals = <A>(xs: Set<A>, ys: Set<A>): boolean => { | |||
if (xs.size != ys.size) { | |||
return false | |||
} | |||
for (const x of xs) { | |||
if (!ys.has(x)) { | |||
return false | |||
} | |||
} | |||
return true | |||
} |
@ -1,143 +0,0 @@ | |||
import type { AssignStmt, Expr, Stmt, UnaryOp, BinaryOp } from "./ast" | |||
type LocationType = "register" | "temporary" | "variable" | |||
type Location = { | |||
type: LocationType, | |||
name: string, | |||
} | |||
type Operand = Location | number | |||
type ASSA<Data> = { dest: Location, source: Operand } & Data | |||
export type SSACopy = ASSA<{}> // dest = source | |||
export type SSAUnary = ASSA<{ op: UnaryOp }> // dest = op(source) | |||
export type SSABinary = ASSA<{ op: BinaryOp, source1: Operand }> // dest = op(source, source1) | |||
export type SSA = SSACopy | SSAUnary | SSABinary | |||
type IRState = { | |||
nextID: number | |||
ssa_stmts: Array<SSA> | |||
} | |||
const temp = (state: IRState): Location => ({ | |||
type: "temporary", | |||
name: `t${state.nextID++}`, | |||
}) | |||
const vari = (name: string): Location => ({ | |||
type: "variable", | |||
name, | |||
}) | |||
const reg = (name: string): Location => ({ | |||
type: "register", | |||
name, | |||
}) | |||
export const convertIR = (stmts: Array<Stmt>): Array<SSA> => { | |||
const state: IRState = { | |||
nextID: 0, | |||
ssa_stmts: [], | |||
} | |||
stmts.forEach(stmt => { | |||
const ssa_stmts = convertAssignStmt(state, stmt) | |||
state.ssa_stmts.push(...ssa_stmts) | |||
}) | |||
return state.ssa_stmts | |||
} | |||
export const convertAssignStmt = (state: IRState, stmt: AssignStmt): Array<SSA> => { | |||
return convertExpr(state, vari(stmt.args.name), stmt.args.expr) | |||
} | |||
export const convertExpr = (state: IRState, dest: Location, expr: Expr): Array<SSA> => { | |||
let expr_stmts = [] | |||
if (typeof expr === "number") { | |||
expr_stmts = [{ dest, source: expr }] | |||
} else if (typeof expr === "string") { | |||
expr_stmts = [{ dest, source: vari(expr) }] | |||
} else if (expr.type === "unary") { | |||
const [source, stmts] = getSource(state, expr.arg) | |||
stmts.push({ | |||
dest, | |||
op: expr.op, | |||
source, | |||
}) | |||
expr_stmts = stmts | |||
} else { | |||
const [left_source, left_stmts] = getSource(state, expr.left) | |||
const [right_source, right_stmts] = getSource(state, expr.right) | |||
const stmts = [...left_stmts, ...right_stmts] | |||
stmts.push({ | |||
dest, | |||
op: expr.op, | |||
source: left_source, | |||
source1: right_source, | |||
}) | |||
expr_stmts = stmts | |||
} | |||
return expr_stmts | |||
} | |||
const getSource = (state: IRState, expr: Expr): [Operand, Array<SSA>] => { | |||
if (typeof expr === "number") { | |||
return [expr, []] | |||
} else if (typeof expr === "string") { | |||
return [vari(expr), []] | |||
} | |||
const source = temp(state) | |||
const stmts = convertExpr(state, source, expr) | |||
return [source, stmts] | |||
} | |||
export const prettyPrintIR = (ssa: SSA): string => { | |||
let output = "" | |||
if ("source1" in ssa) { | |||
output = `${ppLoc(ssa.dest)} = ${ssa.op}(${ppOp(ssa.source)}, ${ppOp(ssa.source1)})` | |||
} else if ("op" in ssa) { | |||
output = `${ppLoc(ssa.dest)} = ${ssa.op}(${ppOp(ssa.source)})` | |||
} else { | |||
output = `${ppLoc(ssa.dest)} = ${ppOp(ssa.source)}` | |||
} | |||
return output | |||
} | |||
const ppOp = (op: Operand): string => | |||
typeof op === "number" | |||
? op.toString() | |||
: ppLoc(op) | |||
const ppLoc = (loc: Location): string => { | |||
let output = '' | |||
switch (loc.type) { | |||
case "register": | |||
output = `%${loc.name}` | |||
break | |||
case "temporary": | |||
output = `#${loc.name}` | |||
break | |||
case "variable": | |||
output = loc.name | |||
break | |||
} | |||
return output | |||
} |
@ -0,0 +1,72 @@ | |||
import type { AssignStmt, Expr, Stmt } from "../ast" | |||
import { Loc } from "./loc" | |||
import type { Operand, SSA } from "./ssa" | |||
type IRState = { | |||
nextID: number | |||
ssa_stmts: Array<SSA> | |||
} | |||
export const convertIR = (stmts: Array<Stmt>): Array<SSA> => { | |||
const state: IRState = { | |||
nextID: 0, | |||
ssa_stmts: [], | |||
} | |||
stmts.forEach(stmt => { | |||
const ssa_stmts = convertAssignStmt(state, stmt) | |||
state.ssa_stmts.push(...ssa_stmts) | |||
}) | |||
return state.ssa_stmts | |||
} | |||
export const convertAssignStmt = (state: IRState, stmt: AssignStmt): Array<SSA> => { | |||
return convertExpr(state, Loc.vari(stmt.args.name), stmt.args.expr) | |||
} | |||
export const convertExpr = (state: IRState, dest: Loc, expr: Expr): Array<SSA> => { | |||
let expr_stmts: Array<SSA> = [] | |||
if (typeof expr === "number") { | |||
expr_stmts = [{ dest, source: expr }] | |||
} else if (typeof expr === "string") { | |||
expr_stmts = [{ dest, source: Loc.vari(expr) }] | |||
} else if (expr.type === "unary") { | |||
const [source, stmts] = getSource(state, expr.arg) | |||
stmts.push({ | |||
dest, | |||
op: expr.op, | |||
source, | |||
}) | |||
expr_stmts = stmts | |||
} else { | |||
const [left_source, left_stmts] = getSource(state, expr.left) | |||
const [right_source, right_stmts] = getSource(state, expr.right) | |||
const stmts = [...left_stmts, ...right_stmts] | |||
stmts.push({ | |||
dest, | |||
op: expr.op, | |||
source: left_source, | |||
source1: right_source, | |||
}) | |||
expr_stmts = stmts | |||
} | |||
return expr_stmts | |||
} | |||
const getSource = (state: IRState, expr: Expr): [Operand, Array<SSA>] => { | |||
if (typeof expr === "number") { | |||
return [expr, []] | |||
} else if (typeof expr === "string") { | |||
return [Loc.vari(expr), []] | |||
} | |||
const source = Loc.temp(`t${state.nextID++}`) | |||
const stmts = convertExpr(state, source, expr) | |||
return [source, stmts] | |||
} |
@ -0,0 +1,43 @@ | |||
export enum LocType { | |||
REGISTER = "register", | |||
TEMPORARY = "temporary", | |||
VARIABLE = "variable", | |||
} | |||
export class Loc { | |||
type: LocType | |||
name: string | |||
constructor(type: LocType, name: string) { | |||
this.type = type | |||
this.name = name | |||
} | |||
toString(): string { | |||
let sigil = '' | |||
switch (this.type) { | |||
case 'register': | |||
sigil = '%' | |||
break | |||
case 'temporary': | |||
sigil = '#' | |||
break | |||
} | |||
return sigil + this.name | |||
} | |||
static vari(name: string): Loc { | |||
return new Loc(LocType.VARIABLE, name) | |||
} | |||
static temp(name: string): Loc { | |||
return new Loc(LocType.TEMPORARY, name) | |||
} | |||
static reg(name: string): Loc { | |||
return new Loc(LocType.REGISTER, name) | |||
} | |||
} |
@ -0,0 +1,45 @@ | |||
import type { Loc } from "./loc" | |||
import type { Operand, SSA } from "./ssa" | |||
export const prettyPrintBlock = (block: Array<SSA>): string => { | |||
return block.map(prettyPrintSSA).join("\n") | |||
} | |||
export const prettyPrintSSA = (ssa: SSA): string => { | |||
let output = "" | |||
if ("source1" in ssa) { | |||
output = `${ppLoc(ssa.dest)} = ${ssa.op}(${ppOp(ssa.source)}, ${ppOp(ssa.source1)})` | |||
} else if ("op" in ssa) { | |||
output = `${ppLoc(ssa.dest)} = ${ssa.op}(${ppOp(ssa.source)})` | |||
} else { | |||
output = `${ppLoc(ssa.dest)} = ${ppOp(ssa.source)}` | |||
} | |||
return output | |||
} | |||
const ppOp = (op: Operand): string => | |||
typeof op === "number" | |||
? op.toString() | |||
: ppLoc(op) | |||
const ppLoc = (loc: Loc): string => { | |||
let output = '' | |||
switch (loc.type) { | |||
case "register": | |||
output = `%${loc.name}` | |||
break | |||
case "temporary": | |||
output = `#${loc.name}` | |||
break | |||
case "variable": | |||
output = loc.name | |||
break | |||
} | |||
return output | |||
} |
@ -0,0 +1,17 @@ | |||
import type { BinaryOp, UnaryOp } from "../ast" | |||
import type { Loc } from "./loc" | |||
export type Operand = Loc | number | |||
type ASSA<Data> = { dest: Loc, source: Operand } & Data | |||
// dest = source | |||
export type SSACopy = ASSA<{}> | |||
// dest = op(source) | |||
export type SSAUnary = ASSA<{ op: UnaryOp }> | |||
// dest = op(source, source1) | |||
export type SSABinary = ASSA<{ op: BinaryOp, source1: Operand }> | |||
export type SSA = SSACopy | SSAUnary | SSABinary |
@ -0,0 +1,33 @@ | |||
import type { Loc } from "./ir/loc" | |||
import type { SSA } from "./ir/ssa" | |||
export type LivenessInfo = Array<Set<Loc>> | |||
export const liveness = (block: Array<SSA>): LivenessInfo => { | |||
const info: LivenessInfo = [] | |||
for (let i = block.length - 1; i >= 0; --i) { | |||
const insn = block[i] | |||
const last = info[i + 1] || new Set() | |||
info[i] = new Set() | |||
if (typeof insn.source !== "number") { | |||
info[i].add(insn.source) | |||
} | |||
if ("source1" in insn && typeof insn.source1 !== "number") { | |||
info[i].add(insn.source1) | |||
} | |||
last.forEach(loc => { | |||
if (loc === insn.dest) { | |||
return | |||
} | |||
info[i].add(loc) | |||
}) | |||
} | |||
return info | |||
} |
@ -1,6 +1,6 @@ | |||
import { expect } from "chai" | |||
import { createGraph, neighbors, vertices } from "../lib/graph" | |||
import { createGraph, neighbors, vertices } from "../../lib/data/graph" | |||
const g = createGraph((v, e) => { | |||
v("a", true) |
@ -1,259 +0,0 @@ | |||
import { expect } from "chai" | |||
import { convertAssignStmt, prettyPrintIR, SSA } from "../lib/ir" | |||
describe("ir", () => { | |||
describe("convertExpr", () => { | |||
it("load with immediate", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: 42, | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: 42, | |||
}, | |||
]) | |||
}) | |||
it("load with variable", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: "y", | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "variable", | |||
name: "y", | |||
}, | |||
}, | |||
]) | |||
}) | |||
it("unary op", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: { | |||
type: "unary", | |||
op: "arith_negate", | |||
arg: "y" | |||
}, | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "variable", | |||
name: "y", | |||
}, | |||
op: "arith_negate", | |||
}, | |||
]) | |||
}) | |||
it("binary op", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: { | |||
type: "binary", | |||
op: "add", | |||
left: "y", | |||
right: 42, | |||
}, | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "variable", | |||
name: "y", | |||
}, | |||
source1: 42, | |||
op: "add", | |||
}, | |||
]) | |||
}) | |||
it("nested binary op", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: { | |||
type: "binary", | |||
op: "add", | |||
left: "y", | |||
right: { | |||
type: "binary", | |||
op: "subtract", | |||
left: "z", | |||
right: 42, | |||
}, | |||
}, | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: { | |||
type: "temporary", | |||
name: "t0", | |||
}, | |||
source: { | |||
type: "variable", | |||
name: "z", | |||
}, | |||
op: "subtract", | |||
source1: 42, | |||
}, | |||
{ | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "variable", | |||
name: "y", | |||
}, | |||
op: "add", | |||
source1: { | |||
type: "temporary", | |||
name: "t0", | |||
}, | |||
}, | |||
]) | |||
}) | |||
}) | |||
describe("prettyPrintIR", () => { | |||
it("load with immediate", () => { | |||
const ssa: SSA = { | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: 42, | |||
} | |||
expect(prettyPrintIR(ssa)).to.equal("x = 42") | |||
}) | |||
it("load with register", () => { | |||
const ssa: SSA = { | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "register", | |||
name: "A" | |||
}, | |||
} | |||
expect(prettyPrintIR(ssa)).to.equal("x = %A") | |||
}) | |||
it("load with temporary", () => { | |||
const ssa: SSA = { | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "temporary", | |||
name: "t0" | |||
}, | |||
} | |||
expect(prettyPrintIR(ssa)).to.equal("x = #t0") | |||
}) | |||
it("load with variable", () => { | |||
const ssa: SSA = { | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "variable", | |||
name: "y" | |||
}, | |||
} | |||
expect(prettyPrintIR(ssa)).to.equal("x = y") | |||
}) | |||
it("unary op", () => { | |||
const ssa: SSA = { | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "variable", | |||
name: "y" | |||
}, | |||
op: "arith_negate", | |||
} | |||
expect(prettyPrintIR(ssa)).to.equal("x = arith_negate(y)") | |||
}) | |||
it("binary op", () => { | |||
const ssa: SSA = { | |||
dest: { | |||
type: "variable", | |||
name: "x", | |||
}, | |||
source: { | |||
type: "variable", | |||
name: "y" | |||
}, | |||
source1: 42, | |||
op: "add", | |||
} | |||
expect(prettyPrintIR(ssa)).to.equal("x = add(y, 42)") | |||
}) | |||
}) | |||
}) |
@ -0,0 +1,133 @@ | |||
import { expect } from "chai" | |||
import { convertAssignStmt } from "../../lib/ir/convert" | |||
import { Loc } from "../../lib/ir/loc" | |||
describe("ir", () => { | |||
describe("convertExpr", () => { | |||
it("load with immediate", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: 42, | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: Loc.vari("x"), | |||
source: 42, | |||
}, | |||
]) | |||
}) | |||
it("load with variable", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: "y", | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: Loc.vari("x"), | |||
source: Loc.vari("y"), | |||
}, | |||
]) | |||
}) | |||
it("unary op", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: { | |||
type: "unary", | |||
op: "arith_negate", | |||
arg: "y" | |||
}, | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: Loc.vari("x"), | |||
source: Loc.vari("y"), | |||
op: "arith_negate", | |||
}, | |||
]) | |||
}) | |||
it("binary op", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: { | |||
type: "binary", | |||
op: "add", | |||
left: "y", | |||
right: 42, | |||
}, | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: Loc.vari("x"), | |||
source: Loc.vari("y"), | |||
source1: 42, | |||
op: "add", | |||
}, | |||
]) | |||
}) | |||
it("nested binary op", () => { | |||
const state = { nextID: 0, ssa_stmts: [] } | |||
const ir = convertAssignStmt(state, { | |||
type: "stmt", | |||
stmt_type: "assign", | |||
args: { | |||
name: "x", | |||
expr: { | |||
type: "binary", | |||
op: "add", | |||
left: "y", | |||
right: { | |||
type: "binary", | |||
op: "subtract", | |||
left: "z", | |||
right: 42, | |||
}, | |||
}, | |||
}, | |||
}) | |||
expect(ir).to.deep.equal([ | |||
{ | |||
dest: Loc.temp("t0"), | |||
source: Loc.vari("z"), | |||
op: "subtract", | |||
source1: 42, | |||
}, | |||
{ | |||
dest: Loc.vari("x"), | |||
source: Loc.vari("y"), | |||
op: "add", | |||
source1: Loc.temp("t0"), | |||
}, | |||
]) | |||
}) | |||
}) | |||
}) |
@ -0,0 +1,66 @@ | |||
import { expect } from "chai" | |||
import { Loc } from "../../lib/ir/loc" | |||
import { prettyPrintSSA } from "../../lib/ir/pretty" | |||
import type { SSA } from "../../lib/ir/ssa" | |||
describe("ir", () => { | |||
describe("prettyPrintSSA", () => { | |||
it("load with immediate", () => { | |||
const ssa: SSA = { | |||
dest: Loc.vari("x"), | |||
source: 42, | |||
} | |||
expect(prettyPrintSSA(ssa)).to.equal("x = 42") | |||
}) | |||
it("load with register", () => { | |||
const ssa: SSA = { | |||
dest: Loc.vari("x"), | |||
source: Loc.reg("A"), | |||
} | |||
expect(prettyPrintSSA(ssa)).to.equal("x = %A") | |||
}) | |||
it("load with temporary", () => { | |||
const ssa: SSA = { | |||
dest: Loc.vari("x"), | |||
source: Loc.temp("t0"), | |||
} | |||
expect(prettyPrintSSA(ssa)).to.equal("x = #t0") | |||
}) | |||
it("load with variable", () => { | |||
const ssa: SSA = { | |||
dest: Loc.vari("x"), | |||
source: Loc.vari("y"), | |||
} | |||
expect(prettyPrintSSA(ssa)).to.equal("x = y") | |||
}) | |||
it("unary op", () => { | |||
const ssa: SSA = { | |||
dest: Loc.vari("x"), | |||
source: Loc.vari("y"), | |||
op: "arith_negate", | |||
} | |||
expect(prettyPrintSSA(ssa)).to.equal("x = arith_negate(y)") | |||
}) | |||
it("binary op", () => { | |||
const ssa: SSA = { | |||
dest: Loc.vari("x"), | |||
source: Loc.vari("y"), | |||
source1: 42, | |||
op: "add", | |||
} | |||
expect(prettyPrintSSA(ssa)).to.equal("x = add(y, 42)") | |||
}) | |||
}) | |||
}) |
@ -0,0 +1,28 @@ | |||
import { expect } from "chai" | |||
import { Loc } from "../lib/ir/loc" | |||
import type { SSA } from "../lib/ir/ssa" | |||
import { liveness } from "../lib/live" | |||
describe("liveness", () => { | |||
it("computes liveness", () => { | |||
const x = [0, 1, 2].map(i => Loc.vari("x" + i)) | |||
const y = [0, 1].map(i => Loc.vari("y" + i)) | |||
const block: Array<SSA> = [ | |||
{ dest: x[0], source: 1 }, | |||
{ dest: x[1], source: x[0], op: "add", source1: x[0] }, | |||
{ dest: x[2], source: x[1], op: "add", source1: x[0] }, | |||
{ dest: y[0], source: x[0], op: "add", source1: x[1] }, | |||
{ dest: y[1], source: y[0], op: "add", source1: x[2] }, | |||
] | |||
const info = liveness(block) | |||
expect(info[0]).to.deep.equal(new Set()) | |||
expect(info[1]).to.deep.equal(new Set([x[0]])) | |||
expect(info[2]).to.deep.equal(new Set([x[0], x[1]])) | |||
expect(info[3]).to.deep.equal(new Set([x[0], x[1], x[2]])) | |||
expect(info[4]).to.deep.equal(new Set([y[0], x[2]])) | |||
}) | |||
}) |