Browse Source

Separate three-address and two-address forms

master
Forest Belton 2 years ago
parent
commit
b588713503
9 changed files with 124 additions and 319 deletions
  1. +45
    -43
      lib/ir/convert.ts
  2. +39
    -0
      lib/ir/insn.ts
  3. +4
    -4
      lib/ir/pretty.ts
  4. +0
    -19
      lib/ir/ssa.d.ts
  5. +8
    -8
      lib/regalloc.ts
  6. +13
    -31
      lib/sm83/codegen.ts
  7. +0
    -133
      test/ir/convert.spec.ts
  8. +0
    -66
      test/ir/pretty.spec.ts
  9. +15
    -15
      test/regalloc.spec.ts

+ 45
- 43
lib/ir/convert.ts View File

@ -1,77 +1,78 @@
import type { AssignStmt, Expr, Stmt } from "../ast"
import { R8 } from "../sm83/cpu"
import { Loc, LocType } from "./loc"
import type { Operand, SSA, SSAWithBinary } from "./ssa"
import type { Operand, AbsInsn2, AbsInsn3 } from "./insn"
type IRState = {
export type IRState = {
nextID: number
ssa_stmts: Array<SSA>
insns: Array<AbsInsn2>
}
export const convertIR = (stmts: Array<Stmt>): Array<SSA> => {
export const convertIR = (stmts: Array<Stmt>): Array<AbsInsn2> => {
const state: IRState = {
nextID: 0,
ssa_stmts: [],
insns: [],
}
stmts.forEach(stmt => {
const ssa_stmts = convertAssignStmt(state, stmt)
state.ssa_stmts.push(...ssa_stmts)
convertAssignStmt(state, stmt)
})
return state.ssa_stmts
return state.insns
}
export const convertAssignStmt = (state: IRState, stmt: AssignStmt): Array<SSA> => {
return convertExpr(state, Loc.vari(stmt.args.name), stmt.args.expr)
export const convertAssignStmt = (state: IRState, stmt: AssignStmt): void => {
convertExpr(state, Loc.vari(stmt.args.name), stmt.args.expr)
}
export const convertExpr = (state: IRState, dest: Loc, expr: Expr): Array<SSA> => {
export const convertExpr = (state: IRState, dest: Loc, expr: Expr): void => {
const threeAddress = convertExpr3(state, dest, expr)
const output: Array<SSA> = []
threeAddress.forEach(ssa => {
// skip copies
if (!("op" in ssa)) {
output.push(ssa)
return
} else if ("source1" in ssa) {
let source1 = ssa.source1
if (typeof source1 !== 'number' && source1.type === LocType.VARIABLE) {
source1 = Loc.temp(`t${state.nextID++}`)
output.push(
{ dest: Loc.reg(R8.A), source: ssa.source1 },
{ dest: source1, source: Loc.reg(R8.A) },
switch (ssa.type) {
case 'copy':
state.insns.push(ssa)
break
case 'unary':
state.insns.push(
{ type: "copy", dest: Loc.reg(R8.A), source: ssa.source },
{ type: "unary", dest: Loc.reg(R8.A), source: Loc.reg(R8.A), op: ssa.op },
{ type: "copy", dest: ssa.dest, source: Loc.reg(R8.A) },
)
}
output.push(
{ dest: Loc.reg(R8.A), source: ssa.source },
{ dest: Loc.reg(R8.A), op: ssa.op, source: source1 },
{ dest: ssa.dest, source: Loc.reg(R8.A) },
)
} else {
output.push(
{ dest: Loc.reg(R8.A), source: ssa.source },
{ dest: Loc.reg(R8.A), source: Loc.reg(R8.A), op: ssa.op },
{ dest: ssa.dest, source: Loc.reg(R8.A) },
)
break
case 'binary':
let source1 = ssa.source1
if (typeof source1 !== 'number' && source1.type === LocType.VARIABLE) {
source1 = Loc.temp(`t${state.nextID++}`)
state.insns.push(
{ type: "copy", dest: Loc.reg(R8.A), source: ssa.source1 },
{ type: "copy", dest: source1, source: Loc.reg(R8.A) },
)
}
state.insns.push(
{ type: "copy", dest: Loc.reg(R8.A), source: ssa.source },
{ type: "unary", dest: Loc.reg(R8.A), op: ssa.op, source: source1 },
{ type: "copy", dest: ssa.dest, source: Loc.reg(R8.A) },
)
break
}
})
return output
}
export const convertExpr3 = (state: IRState, dest: Loc, expr: Expr): Array<SSAWithBinary> => {
let expr_stmts: Array<SSAWithBinary> = []
export const convertExpr3 = (state: IRState, dest: Loc, expr: Expr): Array<AbsInsn3> => {
let expr_stmts: Array<AbsInsn3> = []
if (typeof expr === "number") {
expr_stmts = [{ dest, source: expr }]
expr_stmts = [{ type: "copy", dest, source: expr }]
} else if (typeof expr === "string") {
expr_stmts = [{ dest, source: Loc.vari(expr) }]
expr_stmts = [{ type: "copy", dest, source: Loc.vari(expr) }]
} else if (expr.type === "unary") {
const [source, stmts] = getSource(state, expr.arg)
stmts.push({
type: "unary",
dest,
op: expr.op,
source,
@ -84,6 +85,7 @@ export const convertExpr3 = (state: IRState, dest: Loc, expr: Expr): Array
const stmts = [...left_stmts, ...right_stmts]
stmts.push({
type: "binary",
dest,
op: expr.op,
source: left_source,
@ -96,7 +98,7 @@ export const convertExpr3 = (state: IRState, dest: Loc, expr: Expr): Array
return expr_stmts
}
const getSource = (state: IRState, expr: Expr): [Operand, Array<SSAWithBinary>] => {
const getSource = (state: IRState, expr: Expr): [Operand, Array<AbsInsn3>] => {
if (typeof expr === "number") {
return [expr, []]
} else if (typeof expr === "string") {

+ 39
- 0
lib/ir/insn.ts View File

@ -0,0 +1,39 @@
import type { BinaryOp, UnaryOp } from "../ast"
import type { Loc } from "./loc"
export type Operand = Loc | number
export type AbsInsnCopy = {
type: "copy",
dest: Loc,
source: Operand,
}
export type AbsInsn2Unary = {
type: "unary",
dest: Loc,
// NOTE: We convert binary -> unary when going SSA3 -> SSA2
op: UnaryOp | BinaryOp,
source: Operand,
}
// Abstract instruction in two-address form
export type AbsInsn2 = AbsInsnCopy | AbsInsn2Unary
export type AbsInsn3Unary = {
type: "unary",
dest: Loc,
op: UnaryOp,
source: Operand,
}
export type AbsInsnBinary = {
type: "binary",
dest: Loc,
source: Operand,
op: BinaryOp,
source1: Operand
}
// Abstract instruction in three-address form
export type AbsInsn3 = AbsInsnCopy | AbsInsn3Unary | AbsInsnBinary

+ 4
- 4
lib/ir/pretty.ts View File

@ -1,11 +1,11 @@
import type { Loc } from "./loc"
import type { Operand, SSA, SSAWithBinary } from "./ssa"
import type { AbsInsn2, AbsInsn3, Operand } from "./insn"
export const prettyPrintBlock = (block: Array<SSA>): string => {
return block.map(prettyPrintSSA).join("\n")
export const prettyPrintBlock = <A extends AbsInsn2 | AbsInsn3>(block: Array<A>): string => {
return block.map(prettyPrintInsn).join("\n")
}
export const prettyPrintSSA = (ssa: SSAWithBinary): string => {
export const prettyPrintInsn = (ssa: AbsInsn2 | AbsInsn3): string => {
let output = ""
if ("source1" in ssa) {

+ 0
- 19
lib/ir/ssa.d.ts View File

@ -1,19 +0,0 @@
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 | BinaryOp }>
// dest = op(source, source1)
export type SSABinary = ASSA<{ op: BinaryOp, source1: Operand }>
export type SSA = SSACopy | SSAUnary
export type SSAWithBinary = SSA | SSABinary

+ 8
- 8
lib/regalloc.ts View File

@ -1,11 +1,11 @@
import { colorGreedy, createGraph, Graph, maxCardinalitySearch } from "./data/graph"
import { R8, StackOffset } from "./sm83/cpu"
import type { Loc } from "./ir/loc"
import type { SSA } from "./ir/ssa"
import type { AbsInsn2 } from "./ir/insn"
export type LivenessInfo = Array<Set<Loc>>
export const liveness = (block: Array<SSA>): LivenessInfo => {
export const liveness = (block: Array<AbsInsn2>): LivenessInfo => {
const info: LivenessInfo = []
info[block.length] = new Set()
@ -31,7 +31,7 @@ export const liveness = (block: Array): LivenessInfo => {
return info
}
export const locations = (block: Array<SSA>): Set<Loc> => {
export const locations = (block: Array<AbsInsn2>): Set<Loc> => {
const ls: Set<Loc> = new Set()
block.forEach(ssa => {
@ -45,17 +45,17 @@ export const locations = (block: Array): Set => {
return ls
}
export const interference = (block: Array<SSA>, live: LivenessInfo): Graph<Loc> =>
export const interference = (block: Array<AbsInsn2>, live: LivenessInfo): Graph<Loc> =>
createGraph((v, e) => {
const locs = locations(block)
locs.forEach(loc => {
v(loc.toString(), loc)
})
block.forEach((ssa, i) =>
block.forEach((insn, i) =>
live[i + 1].forEach(u => {
if (ssa.dest.toString() !== u.toString()) {
e(ssa.dest.toString(), u.toString())
if (insn.dest.toString() !== u.toString()) {
e(insn.dest.toString(), u.toString())
}
})
)
@ -65,7 +65,7 @@ export type RegAlloc = {
[s: string]: string | StackOffset,
}
export const allocateRegisters = (block: Array<SSA>, registers: Array<string>): RegAlloc => {
export const allocateRegisters = (block: Array<AbsInsn2>, registers: Array<string>): RegAlloc => {
const info = liveness(block)
const graph = interference(block, info)
const ordering = maxCardinalitySearch(graph)

+ 13
- 31
lib/sm83/codegen.ts View File

@ -1,10 +1,9 @@
import { R8 } from "../sm83/cpu"
import { Loc, LocType } from "../ir/loc"
import type { Operand, SSA, SSABinary, SSACopy, SSAUnary } from "../ir/ssa"
import type { Operand, AbsInsn2, AbsInsnCopy, AbsInsn2Unary } from "../ir/insn"
import type { RegAlloc } from "../regalloc"
import { BinaryOp, UnaryOp } from "../ast"
export const generateBlock = (alloc: RegAlloc, block: Array<SSA>): Array<string> => {
export const generateBlock = (alloc: RegAlloc, block: Array<AbsInsn2>): Array<string> => {
const output: Array<string> = []
block.forEach(ssa => {
@ -14,19 +13,23 @@ export const generateBlock = (alloc: RegAlloc, block: Array): Array
return output
}
export const generateSSA = (alloc: RegAlloc, ssa: SSA): Array<string> => {
export const generateSSA = (alloc: RegAlloc, ssa: AbsInsn2): Array<string> => {
let output: Array<string> = []
if ("op" in ssa) {
output = generateSSA_Unary(alloc, ssa)
} else {
output = generateSSA_Copy(alloc, ssa)
switch (ssa.type) {
case 'copy':
output = generateSSA_Copy(alloc, ssa)
break
case 'unary':
output = generateSSA_Unary(alloc, ssa)
break
}
return output
}
export const generateSSA_Copy = (alloc: RegAlloc, ssa: SSACopy): Array<string> => {
export const generateSSA_Copy = (alloc: RegAlloc, ssa: AbsInsnCopy): Array<string> => {
const output: Array<string> = []
const source = typeof ssa.source === 'number'
@ -76,7 +79,7 @@ const getAlloc = (alloc: RegAlloc, loc: Loc): Loc => {
return Loc.reg(destAlloc)
}
export const generateSSA_Unary = (alloc: RegAlloc, ssa: SSAUnary): Array<string> => {
export const generateSSA_Unary = (alloc: RegAlloc, ssa: AbsInsn2Unary): Array<string> => {
if (!isA(ssa.dest)) {
throw new Error("Unexpected form for unary operation")
}
@ -100,26 +103,5 @@ export const generateSSA_Unary = (alloc: RegAlloc, ssa: SSAUnary): Array
}
}
/*
export const generateSSA_Binary = (alloc: RegAlloc, ssa: SSABinary): Array<string> => {
if (!isA(ssa.dest) || !isA(ssa.source)) {
throw new Error("Unexpected form for binary operation")
}
const output: Array<string> = []
switch (ssa.op) {
case "add":
output.push(`ADD ${source}`)
break
default:
throw new Error(`unsupported binary op \`${ssa.op}'`)
}
return output
} */
const isA = (loc: Operand): boolean =>
typeof loc === "object" && loc.type === LocType.REGISTER && loc.name === R8.A

+ 0
- 133
test/ir/convert.spec.ts View File

@ -1,133 +0,0 @@
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
- 66
test/ir/pretty.spec.ts View File

@ -1,66 +0,0 @@
import { expect } from "chai"
import { Loc } from "../../lib/ir/loc"
import { prettyPrintSSA } from "../../lib/ir/pretty"
import type { SSA, SSAWithBinary } 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: SSAWithBinary = {
dest: Loc.vari("x"),
source: Loc.vari("y"),
source1: 42,
op: "add",
}
expect(prettyPrintSSA(ssa)).to.equal("x = add(y, 42)")
})
})
})

+ 15
- 15
test/regalloc.spec.ts View File

@ -2,7 +2,7 @@ import { expect } from "chai"
import { edgeConnects } from "../lib/data/graph"
import { Loc } from "../lib/ir/loc"
import type { SSA } from "../lib/ir/ssa"
import type { AbsInsn2 } from "../lib/ir/insn"
import { allocateRegisters, interference, liveness } from "../lib/regalloc"
import { R8 } from "../lib/sm83/cpu"
@ -11,12 +11,12 @@ describe("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" },
{ dest: x[2], source: x[1], op: "add" },
{ dest: y[0], source: x[0], op: "add" },
{ dest: y[1], source: y[0], op: "add" },
const block: Array<AbsInsn2> = [
{ type: "copy", dest: x[0], source: 1 },
{ type: "unary", dest: x[1], source: x[0], op: "add" },
{ type: "unary", dest: x[2], source: x[1], op: "add" },
{ type: "unary", dest: y[0], source: x[0], op: "add" },
{ type: "unary", dest: y[1], source: y[0], op: "add" },
]
const info = liveness(block)
@ -31,10 +31,10 @@ describe("liveness", () => {
describe("interference", () => {
it("computes interference", () => {
const block: Array<SSA> = [
{ dest: Loc.vari("a"), source: 7 },
{ dest: Loc.vari("b"), source: 3 },
{ dest: Loc.vari("x"), source: Loc.vari("a") }
const block: Array<AbsInsn2> = [
{ type: "copy", dest: Loc.vari("a"), source: 7 },
{ type: "copy", dest: Loc.vari("b"), source: 3 },
{ type: "copy", dest: Loc.vari("x"), source: Loc.vari("a") },
]
const info = liveness(block)
@ -46,10 +46,10 @@ describe("interference", () => {
describe("allocateRegisters", () => {
it("allocates registers", () => {
const block: Array<SSA> = [
{ dest: Loc.vari("a"), source: 7 },
{ dest: Loc.vari("b"), source: 3 },
{ dest: Loc.vari("x"), source: Loc.vari("a") }
const block: Array<AbsInsn2> = [
{ type: "copy", dest: Loc.vari("a"), source: 7 },
{ type: "copy", dest: Loc.vari("b"), source: 3 },
{ type: "copy", dest: Loc.vari("x"), source: Loc.vari("a") }
]
const alloc = allocateRegisters(block, Object.values(R8))

Loading…
Cancel
Save