Browse Source

Reorganize codebase and implement liveness check

master
Forest Belton 2 years ago
parent
commit
d7060781ae
16 changed files with 471 additions and 437 deletions
  1. +3
    -20
      lib/compile.ts
  2. +3
    -13
      lib/data/graph.ts
  3. +25
    -0
      lib/data/set.ts
  4. +0
    -143
      lib/ir.ts
  5. +72
    -0
      lib/ir/convert.ts
  6. +43
    -0
      lib/ir/loc.ts
  7. +45
    -0
      lib/ir/pretty.ts
  8. +17
    -0
      lib/ir/ssa.d.ts
  9. +33
    -0
      lib/live.ts
  10. +1
    -0
      lib/main.ts
  11. +1
    -1
      package.json
  12. +1
    -1
      test/data/graph.spec.ts
  13. +0
    -259
      test/ir.spec.ts
  14. +133
    -0
      test/ir/convert.spec.ts
  15. +66
    -0
      test/ir/pretty.spec.ts
  16. +28
    -0
      test/live.spec.ts

+ 3
- 20
lib/compile.ts View File

@ -1,7 +1,6 @@
// import { convertASM } from "./asm"
import { convertIR, prettyPrintIR } from "./ir"
import { convertIR } from "./ir/convert"
import { prettyPrintBlock } from "./ir/pretty"
import { parse } from "./parser"
import { inspect } from "util"
import type { Attr, Decl, Stmt, Type, VarDecl } from "./ast"
@ -36,25 +35,9 @@ export const compile = (fileName: string, source: string): string => {
const ir = convertIR(stmts)
console.log("=== IR === ")
console.log(ir.map(prettyPrintIR).join("\n"))
console.log(prettyPrintBlock(ir))
// 5. Generate code
/* const insns = convertASM(ir)
let output = ''
if (asmDecls.length > 0) {
output += `SECTION "${fileName} Data", WRAM0\n\n`
output += asmDecls.join("\n")
output += "\n\n"
}
if (insns.length > 0) {
output += `SECTION "${fileName} Code", ROM0\n\n`
output += insns.join("\n")
}
console.log("=== ASM === ")
return output */
return ''
}

lib/graph.ts → lib/data/graph.ts View File

@ -1,3 +1,5 @@
import { intersect } from "./set"
export type Graph<V> = {
vertices: {
[v: string]: V
@ -55,7 +57,7 @@ export const maxCardinalitySearch = (g: Graph): Array => {
const v = findMaxWeight(weights, W)
ordering.push(v)
setIntersect(W, neighbors(g, v)).forEach(x =>
intersect(W, neighbors(g, v)).forEach(x =>
weights[x] = (weights[x] || 0) + 1
)
@ -83,15 +85,3 @@ const findMaxWeight = (weights: { [s: string]: number }, W: Set): string
return maxV
}
const setIntersect = <A>(xs: Set<A>, ys: Set<A>): Set<A> => {
const intersection: Set<A> = new Set()
xs.forEach(x => {
if (ys.has(x)) {
intersection.add(x)
}
})
return intersection
}

+ 25
- 0
lib/data/set.ts View File

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

+ 0
- 143
lib/ir.ts View File

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

+ 72
- 0
lib/ir/convert.ts View File

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

+ 43
- 0
lib/ir/loc.ts View File

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

+ 45
- 0
lib/ir/pretty.ts View File

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

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

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

+ 33
- 0
lib/live.ts View File

@ -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
- 0
lib/main.ts View File

@ -5,6 +5,7 @@ import { compile } from "./compile"
const { readFile } = promises
// TODO: Proper command-line argument support
if (process.argv.length !== 3) {
console.error("usage: gbuoy <program.gby>")
process.exit(1)

+ 1
- 1
package.json View File

@ -20,7 +20,7 @@
},
"scripts": {
"build": "mkdir -p build && peggy -o build/parser.js lib/parser.pegjs && tsc",
"test": "echo \"Error: no test specified\" && exit 1"
"test": "ts-mocha"
},
"author": "Forest Belton <forest@homolo.gy>",
"license": "MIT",

test/graph.spec.ts → test/data/graph.spec.ts View File

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

+ 0
- 259
test/ir.spec.ts View File

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

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

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

+ 66
- 0
test/ir/pretty.spec.ts View File

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

+ 28
- 0
test/live.spec.ts View File

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

Loading…
Cancel
Save