前回は四則演算を作りました。今回は変数を作ります。
不変な変数
C言語の変数を素直に実装するとポインタを取得する為に、必ずメモリ上に変数を割り当てなくてはなりません。しかし、書き換え不能な変数であればかならずしもメモリ上に領域を取る必要はありません。LLVMのレジスタ数に制限は無いので、ここでは変数はレジスタにのみ取る事にします。LLVMの最適化のフローの中で、レジスタが溢れた場合は、メモリに割り当てる処理を行ってくれるはずです。
以下の例のようにLLVMのレジスタに値をそのまま代入することは出来ません。
%a = i32 10
理由は良くわかりませんが、出来ないものは出来ないので定数は展開する必要があります。ここでは、constFoldというパスを作ってconstFoldでこのようなレジスタに直接値を設定する箇所は展開する事にします。
実装
パッケージ名を変えます。
package chapter03
式のクラスを追加します。
case class EVal(t: T, id: String, a: E) extends E
case class EId(t: T, id: String) extends E
テストコードを追加します。
// 変数 a 定数
EVal(Ti(32), "a", ELdc(Ti(32), 11)),
EPrint(Ti(32), EId(Ti(32), "a")),
// 変数 b 足し算
EVal(Ti(32), "b", EAdd(Ti(32), ELdc(Ti(32), 11), ELdc(Ti(32), 22))),
EPrint(Ti(32), EId(Ti(32), "b")),
// 変数 c 変数の値
EVal(Ti(32), "c", EId(Ti(32), "a")),
EPrint(Ti(32), EId(Ti(32), "c"))
constFoldのパスを追加します。
emit("e.ll", ll)
↓
val ll2 = constFold(ll)
emit("e.ll", ll2)
kNormalにEValの処理を追加します。
case e @ EVal(t, id, a) =>
f(a) match {
case a =>
env.add(RL(t, id))
add(LLAssign(RL(a.t, id), a))
RL(a.t, id)
}
case EId(t, id) => env.map(id)
変数の型を保存する為の環境を作ります。シンボルテーブルとも呼ばれたりするものです。
object env {
var map = Map[String, R]()
def add(r: R) {
map = map + (r.id -> r)
}
}
constFoldを作ります。実体はLLAssignを見つけたらmapに保存してmapに保存されている値を参照していたら、mapからその値を取り出します。
case class LLAssign(s: R, d: R) extends LL
object constFold {
var map: Map[R, R] = null
def m(v: R): R = {
if (map.contains(v)) m(map(v)) else v
}
def fs(prms: List[(T, R)]): List[(T, R)] = {
prms.map {
case (t, v) => (t, m(v))
}
}
def apply(ls: List[LL]): List[LL] = {
map = Map()
ls.foldLeft(List[LL]()) {
case (ls, l @ LLCall(id, op, prms)) => l.copy(prms = fs(prms)) :: ls
case (ls, l @ LLBin(id, op, a, b)) => l.copy(a = m(a), b = m(b)) :: ls
case (ls, l @ LLAssign(s, d)) => map = map + (s -> d); ls
case (ls, l) => throw new Exception("error no implementation "+l)
}.reverse
}
}
ここのapplyを以下のように書き換えると足し算の畳み込みが出来ます。
def apply(ls: List[LL]): List[LL] = {
map = Map()
def f(ls:List[LL]): List[LL] = {
ls.foldLeft(List[LL]()) {
case (ls, l @ LLCall(id, op, prms)) => l.copy(prms = fs(prms)) :: ls
case (ls, l @ LLBin(id, "add", a, b)) =>
(m(a),m(b)) match {
case (RN(t,a),RN(_,b)) =>
f(LLAssign(id,RN(t, a.toLong + b.toLong + ""))::ls)
case (a1,b1) => l.copy(a=a1,b=b1) :: ls
}
case (ls, l @ LLBin(id, op, a, b)) => l.copy(a = m(a), b = m(b)) :: ls
case (ls, l @ LLAssign(s, d)) => map = map + (s -> m(d)); ls
case (ls, l) => throw new Exception("error no implementation "+l)
}.reverse
}
f(ls)
}
実装全体
package chapter03
import java.io._
sealed trait E {
def t:T
}
case class ELdc(t:T, i:Long) extends E
case class EBin(t:T, s:String, l:E, r:E) extends E
case class EPrint(t:T, a:E) extends E
case class EBlock(t: T, ls: List[E]) extends E
case class EVal(t: T, id: String, a: E) extends E
case class EId(t: T, id: String) extends E
sealed trait T
case class Ti(i:Int) extends T
case object Tv extends T
case class TFun(t: T, prms: List[T]) extends T
case class Op(s: String) {
def apply(t: T, a: E, b: E): E = {
EBin(t, s, a, b)
}
}
object EAdd extends Op("add")
object EMul extends Op("mul")
sealed trait R {
def t:T
def id:String
}
case class RG(t:T, id: String) extends R
case class RL(t:T, id: String) extends R
case class RR(t:T, id: String) extends R
case class RN(t:T, id: String) extends R
object test {
def main(argv: Array[String]) {
try {
val ast = EBlock(Tv, List(
EPrint(Ti(32), ELdc(Ti(32), 11)),
EPrint(Ti(32), EAdd(Ti(32), ELdc(Ti(32), 11), ELdc(Ti(32), 22))),
// 変数 a 定数
EVal(Ti(32), "a", ELdc(Ti(32), 11)),
EPrint(Ti(32), EId(Ti(32), "a")),
// 変数 b 足し算
EVal(Ti(32), "b", EAdd(Ti(32), ELdc(Ti(32), 11), ELdc(Ti(32), 22))),
EPrint(Ti(32), EId(Ti(32), "b")),
// 変数 c 変数の値
EVal(Ti(32), "c", EId(Ti(32), "a")),
EPrint(Ti(32), EId(Ti(32), "c"))
))
println("ast=" + ast)
val ll = kNormal(ast)
println("ll=" + ll)
val ll2 = constFold(ll)
emit("e.ll", ll2)
println(exec("llc e.ll -o e.s"))
println(exec("llvm-gcc -m64 e.s -o e"))
println(exec("./e"))
} catch {
case e:Throwable => e.printStackTrace()
}
}
}
object kNormal {
def gid(t:T): R = {
RR(t,genid(""))
}
var ls: List[LL] = null
def add(l: LL) {
ls = l :: ls
}
def f(a: E): R = {
a match {
case EBin(t, op, a1, b1) =>
(f(a1), f(b1), gid(t)) match {
case (a, b, id) =>
if (t != a.t || t != b.t) throw new Exception("type mismatch " + t)
add(LLBin(id, op, a, b))
id
}
case ELdc(t, i) => RN(t, ""+i)
case EPrint(t, a) =>
f(a) match {
case a =>
if (t != a.t) throw new Exception("type mismatch t=" + t + " ta=" + a.t)
add(LLCall(null, RG(TFun(Tv, List(t)), "print_" + emit.llt(t)), List((a.t, a))))
a
}
case EBlock(t, ls) =>
ls.foldLeft(null: R) {
case (tid, l) => f(l)
}
case e @ EVal(t, id, a) =>
f(a) match {
case a =>
env.add(RL(t, id))
add(LLAssign(RL(a.t, id), a))
RL(a.t, id)
}
case EId(t, id) => env.map(id)
}
}
def apply(a: E): List[LL] = {
ls = List[LL]()
f(a)
ls.reverse
}
}
object env {
var map = Map[String, R]()
def add(r: R) {
map = map + (r.id -> r)
}
}
sealed trait LL
case class LLCall(id: R, op: R, prms: List[(T, R)]) extends LL
case class LLBin(id: R, op: String, a: R, b: R) extends LL
case class LLAssign(s: R, d: R) extends LL
object constFold {
var map: Map[R, R] = null
def m(v: R): R = {
if (map.contains(v)) m(map(v)) else v
}
def fs(prms: List[(T, R)]): List[(T, R)] = {
prms.map {
case (t, v) => (t, m(v))
}
}
def apply(ls: List[LL]): List[LL] = {
map = Map()
ls.foldLeft(List[LL]()) {
case (ls, l @ LLCall(id, op, prms)) => l.copy(prms = fs(prms)) :: ls
case (ls, l @ LLBin(id, op, a, b)) => l.copy(a = m(a), b = m(b)) :: ls
case (ls, l @ LLAssign(s, d)) => map = map + (s -> d); ls
case (ls, l) => throw new Exception("error no implementation "+l)
}.reverse
}
}
object emit {
def llt(t:T):String = {
t match {
case Ti(i) => "i" + i
case Tv => "void"
case TFun(t, ls) => llt(t) + "(" + ls.map(llt).mkString(", ") + ")*"
}
}
def llr(r:R): String = {
r match {
case RG(t,id) => "@" + id
case RL(t,id) => "%" + id
case RR(t,id) => "%." + id
case RN(t,id) => "" + id
}
}
def o(id: R, out: String) {
if (id != null) asm(llr(id) + " = " + out)
else asm(out)
}
def f(l: LL) {
l match {
case LLCall(id, op, prms) =>
val ps = prms.map { case (a, b) => llt(a) + " " + llr(b) }.mkString(", ")
o(id, "call " + llt(op.t) + " " + llr(op) + "(" + ps + ") nounwind")
case LLBin(id, op, a, b) =>
o(id, op + " " + llt(id.t) + " " + llr(a) + ", " + llr(b))
}
}
def apply(file: String, ls: List[LL]) {
asm.open(file)
asm.label("@.str = private constant [4 x i8] c\"%d\\0A\\00\"")
asm.label("define void @print_i32(i32 %a) nounwind ssp {")
asm.label("entry:")
asm("call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i32 %a) nounwind")
asm("ret void")
asm.label("}")
asm.label("define void @print_i8(i8 %a) nounwind ssp {")
asm.label("entry:")
asm("call i32 (i8*, ...)* @printf(i8* getelementptr inbounds ([4 x i8]* @.str, i64 0, i64 0), i8 %a) nounwind")
asm("ret void")
asm.label("}")
asm.label("declare i32 @printf(i8*, ...) nounwind")
asm.label("define i32 @main() nounwind ssp {")
asm.label("entry:")
ls.foreach(f)
asm("ret i32 0")
asm.label("}")
asm.close()
}
}
object genid {
var id = 0
def apply(s: String): String = {
id += 1
s + id
}
}
object asm {
var p: PrintWriter = null
def open(file: String) {
p = new PrintWriter(new BufferedWriter(new FileWriter(file)))
}
var indent: String = ""
def apply(s: String, n: String = "") {
val v = indent + s + "\t" + n + "\n"
p.print(v)
}
def label(s: String) {
asm.indent = "";
apply(s)
asm.indent = "\t";
}
def close() {
p.close()
}
}
object exec {
def apply(cmd: String): (Int, String, String) = {
val p = Runtime.getRuntime().exec(cmd)
val stdin = (readAll(p.getInputStream()))
val stderr = (readAll(p.getErrorStream()))
(p.waitFor(), stdin, stderr)
}
def readAll(p: InputStream): String = {
def f(s: String, i: BufferedReader): String = {
i.readLine() match {
case null => s
case a => f(s + a + "\n", i)
}
}
f("", new BufferedReader(new InputStreamReader(p)))
}
}
まとめ
今回は、不変な変数を実装しました。constFoldパスを追加して、kNormal->constFold->emitという3つのパスで構成されるようになりました。次回は、構造体を作成します。