코드 생성 연습 문서의 문제 2 풀이 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
# 코드 생성 연습 문제 2: if-else를 단순화한 MIPS 명령으로.
# 실행: python3 53_codegen-practice_p2.py (표준 라이브러리만 쓴다)
# 흉내기(Machine)는 51_mips-instructions_impl.py의 것을 복사했다.
import re
CMP = {"eq": lambda a, b: a == b, "ne": lambda a, b: a != b, "gt": lambda a, b: a > b,
"ge": lambda a, b: a >= b, "lt": lambda a, b: a < b, "le": lambda a, b: a <= b}
ARITH = {"add": lambda a, b: a + b, "sub": lambda a, b: a - b, "mul": lambda a, b: a * b,
"div": lambda a, b: int(a / b)}
class Machine:
def __init__(self, program, regs=None, mem=None):
self.code = [ln.strip() for ln in program if ln.strip()]
self.labels = {ln[:-1]: i for i, ln in enumerate(self.code) if ln.endswith(":")}
self.reg = dict(regs or {})
self.mem = dict(mem or {})
self.pc = 0
self.loads = self.stores = self.steps = 0
def addr(self, s):
off, base = re.fullmatch(r"(-?\d+)\((\$\w+)\)", s).groups()
return self.reg[base] + int(off)
def step(self):
ln = self.code[self.pc]
self.pc += 1
if ln.endswith(":"):
return
self.steps += 1
op, *a = ln.split()
R = self.reg
if op == "lw":
R[a[0]] = self.mem[self.addr(a[1])]
self.loads += 1
elif op == "sw":
self.mem[self.addr(a[1])] = R[a[0]]
self.stores += 1
elif op == "li":
R[a[0]] = int(a[1])
elif op in ARITH:
R[a[0]] = ARITH[op](R[a[1]], R[a[2]])
elif op[:-1] in ARITH and op.endswith("i"):
R[a[0]] = ARITH[op[:-1]](R[a[1]], int(a[2]))
elif op[0] == "s" and op[1:] in CMP:
R[a[0]] = int(CMP[op[1:]](R[a[1]], R[a[2]]))
elif op[0] == "s" and op.endswith("i") and op[1:-1] in CMP:
R[a[0]] = int(CMP[op[1:-1]](R[a[1]], int(a[2])))
elif op == "j":
self.pc = self.labels[a[0]]
elif op in ("beqz", "bnez"):
if CMP[op[1:3]](R[a[0]], 0):
self.pc = self.labels[a[1]]
elif op[0] == "b" and op.endswith("i") and op[1:-1] in CMP:
if CMP[op[1:-1]](R[a[0]], int(a[1])):
self.pc = self.labels[a[2]]
elif op[0] == "b" and op[1:] in CMP:
if CMP[op[1:]](R[a[0]], R[a[1]]):
self.pc = self.labels[a[2]]
elif op == "jr":
self.pc = R[a[0]]
elif op == "jal": # 문서의 명령 표에 없는 명령. 실제 MIPS의 jal처럼 돌아올 자리를 $ra에 넣고 뛴다
R["$ra"] = self.pc
self.pc = self.labels[a[0]]
else:
raise ValueError(ln)
def run(self, limit=100000):
while self.pc < len(self.code):
self.step()
limit -= 1
assert limit > 0, "끝나지 않는다"
return self
# 3주소 코드: t0 = x == 0 / ifnot t0 goto L1 / y = 1 / goto L2 / L1: / y = 2 / L2:
# 스택: t0는 $sp+12, x는 $sp+8, y는 $sp+4
ASM = ["lw $r0 8($sp)", "seqi $r1 $r0 0", "sw $r1 12($sp)", # t0 = x == 0
"lw $r0 12($sp)", "beqz $r0 L1", # ifnot t0 goto L1
"li $r0 1", "sw $r0 4($sp)", # y = 1
"j L2", # goto L2
"L1:",
"li $r0 2", "sw $r0 4($sp)", # y = 2
"L2:"]
if __name__ == "__main__":
SP = 1000
for x in range(-3, 4):
m = Machine(ASM, {"$sp": SP}, {SP + 8: x}).run()
assert m.mem[SP + 4] == (1 if x == 0 else 2)
assert m.mem[SP + 12] == int(x == 0)
# 어느 길로 가든 메모리 읽기 2번, 쓰기 2번
assert (m.loads, m.stores) == (2, 2)
# j L2를 빼먹으면 x == 0일 때 y = 1 뒤에 y = 2까지 실행된다
broken = [ln for ln in ASM if ln != "j L2"]
assert Machine(broken, {"$sp": SP}, {SP + 8: 0}).run().mem[SP + 4] == 2
print("ALL CHECKS PASSED")