mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 19:19:58 -04:00
145 lines
6.5 KiB
Python
145 lines
6.5 KiB
Python
"""Function-local NZCV for recomp/gen.py.
|
|
|
|
The lifter writes flags the way the guest does: every flag-setting instruction packs N, Z, C and V into c->nzcv
|
|
(subs64() and friends in runtime/cpu.h) and every reader unpacks them (COND_xx(c)). Compilers keep NZCV inside a
|
|
function, and AAPCS64 neither passes it to a callee nor preserves it across a call. So a generated function keeps its
|
|
flags in a local `Flags F` (runtime/cpu.h), which clang holds in registers, and writes c->nzcv only where it leaves
|
|
for another function other than by a call or a return (see plan()).
|
|
|
|
A flag-setting instruction of a known kind (subtract, add or logic result; 32 or 64 bits) stores its operands in F.a
|
|
and F.b. A reader all of whose reaching definitions are of one kind computes its condition from those operands, so
|
|
`cmp x1, x2; b.lt` becomes a signed compare in C. Instructions whose result is only known packed (CCMP, FCMP, MSR
|
|
NZCV, ADCS) write F.nz, the packed form; a reader whose definitions are of several kinds reads F.nz, and those
|
|
definitions then also write it.
|
|
|
|
A read that can see flags a callee left reads the caller's flags from before the call instead. AAPCS64 code never
|
|
reads a callee's flags, so such a read only sits on a path that does not run: in NX2navi it follows calls to noreturn
|
|
functions (asserts) that the compiler placed before another branch's target. Plan.after_call counts them.
|
|
"""
|
|
import re
|
|
|
|
KIND_DEF = re.compile(r'\b(subs|adds|logic)(32|64)\(c, ')
|
|
CARRY_DEF = re.compile(r'\b(adcs|sbcs)(32|64)\(c, ')
|
|
CARRY_USE = re.compile(r'\b(adc|sbc)(32|64)\(c, ')
|
|
COND = re.compile(r'COND_([A-Z]{2})\(c\)')
|
|
NZ_DEF = 'c->nzcv = '
|
|
NZ_USE = '(c->nzcv)'
|
|
KIND_NAME = {'subs': 'sub', 'adds': 'add', 'logic': 'logic'}
|
|
|
|
ENTRY, CALL = 'entry', 'call'
|
|
|
|
|
|
class FlagError(Exception):
|
|
pass
|
|
|
|
|
|
def effect(code):
|
|
"""(reads flags, kind of the flags written or None) of one instruction's C."""
|
|
reads = bool(COND.search(code) or CARRY_USE.search(code) or CARRY_DEF.search(code) or NZ_USE in code)
|
|
if NZ_DEF in code or CARRY_DEF.search(code):
|
|
return reads, 'nz'
|
|
defs = KIND_DEF.findall(code)
|
|
if len(defs) > 1:
|
|
raise FlagError(f'several flag definitions in one instruction: {code}')
|
|
if defs:
|
|
op, size = defs[0]
|
|
return reads, KIND_NAME[op] + size
|
|
return reads, None
|
|
|
|
|
|
class Plan:
|
|
"""Per instruction: the kind its flag reads use, whether its flag definition also writes F.nz, and what it writes
|
|
to c->nzcv before it leaves for another function."""
|
|
|
|
def __init__(self, n):
|
|
self.reader = [None] * n
|
|
self.pack = [False] * n
|
|
self.exit = [None] * n # kind of the flags to publish in c->nzcv at a tail exit, or None
|
|
self.entry = False # F.nz starts as c->nzcv: flags from the entry reach a read or an exit
|
|
self.entry_read = False # they reach a read, not only an exit
|
|
self.after_call = 0 # reads that can see flags a callee left (see the module docstring)
|
|
self.used = False
|
|
|
|
def decl(self):
|
|
"""Declaration of F at the top of the function."""
|
|
return 'Flags F = {.nz = c->nzcv};' if self.entry else 'Flags F = {0};'
|
|
|
|
def publish(self, i):
|
|
"""Statement writing the flags to c->nzcv at instruction i's tail exit ('' when it need not)."""
|
|
k = self.exit[i]
|
|
return f'c->nzcv = fnz_{k}(&F); ' if k else ''
|
|
|
|
def rewrite(self, i, code):
|
|
"""code of instruction i with its flag reads and writes on F."""
|
|
rk = self.reader[i]
|
|
_, dk = effect(code)
|
|
if rk:
|
|
code = COND.sub(lambda m: f'fc_{rk}(&F, CC_{m.group(1)})', code)
|
|
code = CARRY_DEF.sub(lambda m: f'{m.group(1)}{m.group(2)}f(&F, fc_{rk}(&F, CC_CS), ', code)
|
|
code = CARRY_USE.sub(lambda m: f'{m.group(1)}{m.group(2)}f(fc_{rk}(&F, CC_CS), ', code)
|
|
code = code.replace(NZ_USE, f'(fnz_{rk}(&F))')
|
|
if dk == 'nz':
|
|
code = code.replace(NZ_DEF, 'F.nz = ')
|
|
code = re.sub(r'\(void\)(subs|adds)(32|64)\(c, ', lambda m: f'F.nz = nz_{KIND_NAME[m.group(1)]}{m.group(2)}(',
|
|
code)
|
|
elif dk:
|
|
code = KIND_DEF.sub(lambda m: f'f{KIND_NAME[m.group(1)]}{m.group(2)}(&F, ', code)
|
|
if self.pack[i]:
|
|
code += f' F.nz = fnz_{dk}(&F);'
|
|
if 'c->nzcv' in code or COND.search(code) or KIND_DEF.search(code) or CARRY_USE.search(code):
|
|
raise FlagError(f'flag access left after the rewrite: {code}')
|
|
return code
|
|
|
|
|
|
def plan(effects, succ, calls, exits):
|
|
"""effects[i]: (reads, kind or None); succ[i]: successor indices inside the function; calls: indices of calls,
|
|
whose callee may leave any flags; exits: indices that can leave for another function other than by a call or a
|
|
return (a tail branch, falling into the next function, a br through rt_dispatch).
|
|
|
|
Discovery can start a function inside another one (a branch target, a materialised address), and the code
|
|
before it then falls or branches into it with live flags. So a tail exit publishes the flags in c->nzcv, and a
|
|
function whose reads can see its entry flags starts F.nz from c->nzcv. Calls and returns publish nothing: AAPCS64
|
|
code neither passes flags to a callee nor reads a callee's, so a call's flags count as no definition."""
|
|
n = len(effects)
|
|
p = Plan(n)
|
|
if not any(r or k for r, k in effects):
|
|
return p
|
|
p.used = True
|
|
reach = [set() for _ in range(n)]
|
|
reach[0].add(ENTRY)
|
|
work = [0]
|
|
while work:
|
|
i = work.pop()
|
|
out = {i} if effects[i][1] else ({CALL} if i in calls else reach[i])
|
|
for s in succ[i]:
|
|
if not out <= reach[s]:
|
|
reach[s] |= out
|
|
work.append(s)
|
|
|
|
def kind(d):
|
|
return 'nz' if d == ENTRY else effects[d][1]
|
|
|
|
def use(r):
|
|
"""Kind a use of the definitions r reads; several kinds read the packed form, which they then all write."""
|
|
kinds = {kind(d) for d in r}
|
|
if ENTRY in r:
|
|
p.entry = True
|
|
if len(kinds) == 1:
|
|
return kinds.pop()
|
|
for d in r:
|
|
if d != ENTRY:
|
|
p.pack[d] = True
|
|
return 'nz' # several kinds, or none (unreachable)
|
|
|
|
for i, (reads, _) in enumerate(effects):
|
|
if reads:
|
|
p.after_call += CALL in reach[i]
|
|
p.reader[i] = use(reach[i] - {CALL})
|
|
p.entry_read |= ENTRY in reach[i]
|
|
for i in exits:
|
|
# Entry flags alone are still in c->nzcv.
|
|
r = reach[i] - {CALL}
|
|
if r and r != {ENTRY}:
|
|
p.exit[i] = use(r)
|
|
return p
|