def MakeGenericCpuRegs(num_gpr_not_lac, num_gpr_lac=0, num_flt_not_lac=0, num_flt_lac=0): return ( [ir.CpuReg(f"gn{i}", i, GPR_NOT_LAC) for i in range(num_gpr_not_lac)] + [ir.CpuReg(f"gl{i}", i, GPR_LAC) for i in range(num_gpr_lac)] + [ir.CpuReg(f"sn{i}", i, FLT_NOT_LAC) for i in range(num_flt_not_lac)] + [ir.CpuReg(f"sl{i}", i, FLT_LAC) for i in range(num_flt_lac)])
def testD(self): code = io.StringIO(r""" .fun arm_syscall_write SIGNATURE [S32] = [S32 A32 U32] .fun putchar NORMAL [] = [U32] .fun writeln NORMAL [] = [A32 U32] # live_out: ['r0', 'r1'] .reg S32 [$r0_S32 dummy] .reg U32 [$r0_U32 $r1_U32 $r2_U32 len] .reg A32 [$r0_A32 $r1_A32 buf] .bbl start mov buf $r0_A32@r0 # 0 mov len $r1_U32@r1 # 1 mov $r2_U32@r2 len # 2 mov $r1_A32@r1 buf # 3 mov $r0_S32@r0 1 # 4 syscall arm_syscall_write 4:U32 # 5 mov dummy $r0_S32@r0 # 6 mov $r0_U32@r0 10 # 7 bsr putchar # 8 ret # 9 """) cpu_regs = {"r0": ir.CpuReg("r0", 0), "r1": ir.CpuReg("r1", 1), "r2": ir.CpuReg("r2", 2)} unit = serialize.UnitParseFromAsm(code, cpu_regs=cpu_regs) fun = unit.fun_syms["arm_syscall_write"] fun.cpu_live_out = {cpu_regs["r0"]} fun.cpu_live_in = {cpu_regs["r0"], cpu_regs["r1"], cpu_regs["r2"]} fun = unit.fun_syms["putchar"] fun.cpu_live_in = {cpu_regs["r0"]} fun = unit.fun_syms["writeln"] cfg.FunSplitBbls(fun) cfg.FunInitCFG(fun) cfg.FunRemoveUnconditionalBranches(fun) cfg.FunRemoveEmptyBbls(fun) liveness.FunComputeLivenessInfo(fun) ranges = liveness.BblGetLiveRanges(fun.bbls[0], fun, fun.bbls[0].live_out, False) ranges.sort() print("TestD") for lr in ranges: print(lr) self.assertEqual(ranges, [ liveness.LiveRange(liveness.BEFORE_BBL, 0, fun.reg_syms["$r0_A32"], 1), liveness.LiveRange(liveness.BEFORE_BBL, 1, fun.reg_syms["$r1_U32"], 1), liveness.LiveRange(0, 3, fun.reg_syms["buf"], 1), liveness.LiveRange(1, 2, fun.reg_syms["len"], 1), liveness.LiveRange(2, 5, fun.reg_syms["$r2_U32"], 0), liveness.LiveRange(3, 5, fun.reg_syms["$r1_A32"], 0), liveness.LiveRange(4, 5, fun.reg_syms["$r0_S32"], 0), liveness.LiveRange(5, 6, fun.reg_syms["$r0_S32"], 1), liveness.LiveRange(6, liveness.NO_USE, fun.reg_syms["dummy"], 0), liveness.LiveRange(7, 8, fun.reg_syms["$r0_U32"], 0), ])
def get_available_reg(self, lr: liveness.LiveRange) -> ir.CpuReg: key: REG_KIND_LAC = self._get_reg_class(lr) available = self._available.get(key) if available: return available.pop(-1) else: # manufacture a new register self.counter += 1 return ir.CpuReg(f"z{self.counter}", key[1], key[0].value)
def testE(self): code = io.StringIO(r""" .fun test NORMAL [F32 F32 F32 F32] = [F32 F32] .reg F32 [a b add sub mul div $s0_F32 $s1_F32 $s2_F32 $s3_F32] .bbl start mov a $s0_F32@s0 mov b $s1_F32@s1 add add a b sub sub a b mul mul a b div div a b mov $s3_F32@s3 div mov $s2_F32@s2 mul mov $s1_F32@s1 sub mov $s0_F32@s0 add ret """) cpu_regs = { "s0": ir.CpuReg("s0", 0), "s1": ir.CpuReg("s1", 1), "s2": ir.CpuReg("s2", 2), "s3": ir.CpuReg("s3", 2) } unit = serialize.UnitParseFromAsm(code, cpu_regs=cpu_regs) fun = unit.fun_syms["test"] fun.cpu_live_out = { cpu_regs["s0"], cpu_regs["s1"], cpu_regs["s2"], cpu_regs["s3"] } fun.cpu_live_in = {cpu_regs["s0"], cpu_regs["s1"]} cfg.FunSplitBblsAtTerminators(fun) cfg.FunInitCFG(fun) cfg.FunRemoveUnconditionalBranches(fun) cfg.FunRemoveEmptyBbls(fun) liveness.FunComputeLivenessInfo(fun) ranges = liveness.BblGetLiveRanges(fun.bbls[0], fun, fun.bbls[0].live_out) ranges.sort() print("TestE") for lr in ranges: print(lr)
def get_available_reg(self, lr: liveness.LiveRange) -> ir.CpuReg: key: REG_KIND_LAC = self._get_reg_class(lr) available = self._available.get(key) if available: cpu_reg = available.pop(-1) else: # manufacture a new register self.counter += 1 cpu_reg = ir.CpuReg(f"z{self.counter}", key[1], key[0]) if TRACE_REG_ALLOC: print(f"{cpu_reg.name} {key} <- {lr}") return cpu_reg
from Base import ir from Base.liveness import LiveRange, BblGetLiveRanges, BEFORE_BBL, AFTER_BBL, LiveRangeFlag from Base import opcode_tab as o from Base import serialize from Base import reg_alloc O = o.Opcode.Lookup LAC = 16 GPR_NOT_LAC = 1 GPR_LAC = LAC + GPR_NOT_LAC FLT_NOT_LAC = 2 FLT_LAC = LAC + FLT_NOT_LAC A32_REGS = ( [ir.CpuReg(f"r{i}", i, GPR_NOT_LAC) for i in range(6)] + [ir.CpuReg(f"r12", 12, GPR_NOT_LAC), ir.CpuReg(f"r14", 14, GPR_NOT_LAC)] + [ir.CpuReg(f"r{i}", i, GPR_LAC) for i in range(6, 12)] + [ir.CpuReg(f"s{i}", i, FLT_NOT_LAC) for i in range(16)] + [ir.CpuReg(f"s{i}", i, FLT_LAC) for i in range(16, 32)]) def DumpBbl(bbl): lines = serialize.BblRenderToAsm(bbl) print(lines.pop(0)) for n, l in enumerate(lines): print(f"{n:2d}", l) def extract_lsb(x):
from Base import serialize import dataclasses from typing import List, Optional, Tuple import enum # This must mimic the DK enum (0: invalid, no more than 255 entries) @enum.unique class CpuRegKind(enum.Enum): INVALID = 0 GPR = 1 FLT = 2 _GPR_REGS = [ir.CpuReg(f"x{i}" if i != 31 else "sp", i, CpuRegKind.GPR) for i in range(32)] _FLT_REGS = [ir.CpuReg(f"d{i}", i, CpuRegKind.FLT) for i in range(32)] # used to map function parameters to CpuRegs and for non-lac regs # note: our calling convention does not completely match the official one _GPR_PARAMETER_REGS = _GPR_REGS[0:16] _FLT_PARAMETER_REGS = _FLT_REGS[0:8] + _FLT_REGS[16:32] CPU_REGS_MAP = {**{r.name: r for r in _GPR_REGS}, **{r.name: r for r in _FLT_REGS}} REG_KIND_TO_CPU_REG_FAMILY = { o.DK.S8: CpuRegKind.GPR, o.DK.S16: CpuRegKind.GPR,
from typing import List, Optional, Tuple import enum _GPR_REG_NAMES = ["r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "sp", "lr", "pc"] @enum.unique class A32RegKind(enum.IntEnum): INVALID = 0 GPR = 1 FLT = 2 DBL = 2 + 16 _GPR_REGS = [ir.CpuReg(name, i, A32RegKind.GPR) for i, name in enumerate(_GPR_REG_NAMES)] _FLT_REGS = [ir.CpuReg(f"s{i}", i, A32RegKind.FLT) for i in range(32)] DBL_REGS = [ir.CpuReg(f"d{i}", i, A32RegKind.DBL) for i in range(16)] CPU_REGS = {**{r.name: r for r in _GPR_REGS}, **{r.name: r for r in _FLT_REGS}, **{r.name: r for r in DBL_REGS}} def A32RegToAllocMask(reg: ir.CpuReg) -> int: """ Note that for DBL and FLT this matches the architectural overlap For GPR this produces the right mask for ldm/stm """ if reg.kind is A32RegKind.DBL:
# This must mimic the DK enum (0: invalid, no more than 255 entries) @enum.unique class CpuRegKind(enum.IntEnum): INVALID = 0 GPR = 1 FLT = 2 DBL = 2 + 16 GPR_FAMILY = 1 FLT_FAMILY = 2 # (includes FLT + DBL ) GPR_REGS = [ ir.CpuReg(name, i, CpuRegKind.GPR) for i, name in enumerate(_GPR_REG_NAMES) ] FLT_REGS = [ir.CpuReg(f"s{i}", i, CpuRegKind.FLT) for i in range(32)] DBL_REGS = [ir.CpuReg(f"d{i}", i, CpuRegKind.DBL) for i in range(16)] CPU_REGS_MAP = { **{r.name: r for r in GPR_REGS}, **{r.name: r for r in FLT_REGS}, **{r.name: r for r in DBL_REGS} } def A32RegToAllocMask(reg: ir.CpuReg) -> int:
o.DK.U16: GPR_FAMILY, o.DK.U32: GPR_FAMILY, o.DK.U64: GPR_FAMILY, # o.DK.A64: GPR_FAMILY, o.DK.C64: GPR_FAMILY, # o.DK.F32: FLT_FAMILY, o.DK.F64: FLT_FAMILY, } # We use the 64 bit reg names regardless of the operand width _REG_NAMES = ["rax", "rcx", "rdx", "rbx", "sp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"] _GPR_REGS = [ir.CpuReg(name, i, CpuRegKind.GPR) for i, name in enumerate(_REG_NAMES)] _FLT_REGS = [ir.CpuReg(f"xmm{i}", i, CpuRegKind.FLT) for i in range(16)] CPU_REGS_MAP = {**{r.name: r for r in _GPR_REGS}, **{r.name: r for r in _FLT_REGS}} GPR_RESERVED_MASK = 0x0011 # rax/sp is not available for allocation GPR_REGS_MASK = 0xffee GPR_LAC_REGS_MASK = 0xf028 # rbx, rbp, r12-r15 GPR_REG_IMPLICIT_MASK = 0x0007 # rax/rcx/rdx must not be used for globals FLT_RESERVED_MASK = 0x0001 # xmm0 is not available for allocation FLT_REGS_MASK = 0xfffe FLT_LAC_REGS_MASK = 0xff00 # xmm8 - xmm15
def testD(self): code = io.StringIO(r""" .fun arm_syscall_write SIGNATURE [S32] = [S32 A32 U32] .fun putchar NORMAL [] = [U32] .fun writeln NORMAL [] = [A32 U32] # live_out: ['r0', 'r1'] .reg S32 [$r0_S32 dummy] .reg U32 [$r0_U32 $r1_U32 $r2_U32 len] .reg A32 [$r0_A32 $r1_A32 buf] .bbl start mov buf $r0_A32@r0 # 0 mov len $r1_U32@r1 # 1 mov $r2_U32@r2 len # 2 mov $r1_A32@r1 buf # 3 mov $r0_S32@r0 1 # 4 syscall arm_syscall_write 4:U32 # 5 mov dummy $r0_S32@r0 # 6 mov $r0_U32@r0 10 # 7 bsr putchar # 8 ret # 9 """) cpu_regs = { "r0": ir.CpuReg("r0", 0), "r1": ir.CpuReg("r1", 1), "r2": ir.CpuReg("r2", 2) } unit = serialize.UnitParseFromAsm(code, cpu_regs=cpu_regs) fun = unit.fun_syms["arm_syscall_write"] fun.cpu_live_out = {cpu_regs["r0"]} fun.cpu_live_in = {cpu_regs["r0"], cpu_regs["r1"], cpu_regs["r2"]} fun = unit.fun_syms["putchar"] fun.cpu_live_in = {cpu_regs["r0"]} fun = unit.fun_syms["writeln"] cfg.FunSplitBblsAtTerminators(fun) cfg.FunInitCFG(fun) cfg.FunRemoveUnconditionalBranches(fun) cfg.FunRemoveEmptyBbls(fun) liveness.FunComputeLivenessInfo(fun) ranges = liveness.BblGetLiveRanges(fun.bbls[0], fun, fun.bbls[0].live_out) ranges.sort() print("TestD") for lr in ranges: print(lr) lr_r0 = liveness.LiveRange(liveness.BEFORE_BBL, 0, fun.reg_syms["$r0_A32"], 1) lr_r1 = liveness.LiveRange(liveness.BEFORE_BBL, 1, fun.reg_syms["$r1_U32"], 1) lr_buf = liveness.LiveRange(0, 3, fun.reg_syms["buf"], 1) lr_len = liveness.LiveRange(1, 2, fun.reg_syms["len"], 1) lr_r0_2 = liveness.LiveRange(5, 6, fun.reg_syms["$r0_S32"], 1) expected = [ lr_r0, lr_r1, liveness.LiveRange(0, 0, reg=ir.REG_INVALID, num_uses=1, uses=[lr_r0]), lr_buf, liveness.LiveRange(1, 1, reg=ir.REG_INVALID, num_uses=1, uses=[lr_r1]), lr_len, liveness.LiveRange(2, 2, reg=ir.REG_INVALID, num_uses=1, uses=[lr_len]), liveness.LiveRange(2, 5, fun.reg_syms["$r2_U32"], 0), liveness.LiveRange(3, 3, reg=ir.REG_INVALID, num_uses=1, uses=[lr_buf]), liveness.LiveRange(3, 5, fun.reg_syms["$r1_A32"], 0), liveness.LiveRange(4, 5, fun.reg_syms["$r0_S32"], 0), lr_r0_2, liveness.LiveRange(6, 6, reg=ir.REG_INVALID, num_uses=1, uses=[lr_r0_2]), liveness.LiveRange(6, liveness.NO_USE, fun.reg_syms["dummy"], 0), liveness.LiveRange(7, 8, fun.reg_syms["$r0_U32"], 0), ] # self.assertSequenceEqual(ranges, expected) # this does not work because of the uses field self.assertEqual(len(ranges), len(expected)) for a, b in zip(): self.assertEqual(a, b)