From 5d53893bceeff36e873b587d428981f1e3cee31e Mon Sep 17 00:00:00 2001 From: Dan Phung Date: Fri, 31 Jul 2026 23:43:56 -0700 Subject: [PATCH 1/3] ASTI: derive address-keyed register-liveness from reaching-def facts Extend ASTILiveness with register_liveness, computing GP-register live-in/live-out per instruction address from the per-instruction reaching-def facts (restricted to the architecture register set to exclude spill slots and stack temporaries), reusing the shared use/kill and backward-fixpoint engine already used for flag-liveness. --- chb/astinterface/ASTILiveness.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/chb/astinterface/ASTILiveness.py b/chb/astinterface/ASTILiveness.py index 804e67bd..699b9ace 100644 --- a/chb/astinterface/ASTILiveness.py +++ b/chb/astinterface/ASTILiveness.py @@ -26,15 +26,16 @@ # ------------------------------------------------------------------------------ """Address-keyed liveness derived from per-instruction reaching-def facts. -The flag-reaching-definition facts that CodeHawk attaches to each instruction -record, at each USE site, the addresses that DEFINE the flag value used there. -From those facts this class builds per-address use/kill sets and runs a standard -backward live-variable fixpoint over the function CFG, producing live-in/live-out -sets keyed by instruction address. +The reaching-definition facts that CodeHawk attaches to each instruction record, +at each USE site, the addresses that DEFINE the value used there (for GP +registers via the reaching-def facts, for NZCV flags via the flag-reaching-def +facts). From those facts this class builds per-address use/kill sets and runs a +standard backward live-variable fixpoint over the function CFG, producing +live-in/live-out sets keyed by instruction address. This is intentionally sound-by-over-approximation: every use recorded in the facts is honored (never dropped), while a def that reaches no use may be absent -from the kill set, which can only make a flag appear live longer -- never +from the kill set, which can only make a variable appear live longer -- never shorter. Consumers that use liveness to gate a transformation therefore never get a false "dead". """ @@ -66,6 +67,18 @@ def flag_liveness(self) -> Dict[str, Dict[str, List[str]]]: lambda instr: instr.xdata.flag_reachingdefs) return self._liveness(use, kill) + def register_liveness( + self, registers: Set[str]) -> Dict[str, Dict[str, List[str]]]: + """GP-register live-in/live-out per instruction address. + + registers is the set of architecture register names (e.g. the keys of + the register-size map), used to exclude spill slots and stack + temporaries. + """ + (use, kill) = self._use_kill( + lambda instr: instr.xdata.reachingdefs, set(registers)) + return self._liveness(use, kill) + def _use_kill( self, get_facts: Callable[ From 7af5105adc9b6b60b7d00fe4d54ac679706413c8 Mon Sep 17 00:00:00 2001 From: Dan Phung Date: Fri, 31 Jul 2026 23:43:56 -0700 Subject: [PATCH 2/3] AST: carry and serialize address-keyed register-liveness in provenance Add a register-liveness map (keyed by instruction address) to ASTProvenance with the standard getter/setter, and round-trip it through serialize/deserialize alongside the other provenance facts. --- chb/ast/ASTProvenance.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/chb/ast/ASTProvenance.py b/chb/ast/ASTProvenance.py index 29711a49..5efd3293 100644 --- a/chb/ast/ASTProvenance.py +++ b/chb/ast/ASTProvenance.py @@ -38,9 +38,10 @@ def __init__(self) -> None: self._reaching_definitions: Dict[int, List[int]] = {} self._flag_reaching_definitions: Dict[int, List[int]] = {} self._definitions_used: Dict[int, List[int]] = {} - # NZCV flag liveness, keyed by instruction address: + # Liveness, keyed by instruction address: # {"0xNNNN": {"live-in": [...], "live-out": [...]}} self._flag_liveness: Dict[str, Dict[str, List[str]]] = {} + self._register_liveness: Dict[str, Dict[str, List[str]]] = {} @property def instruction_mapping(self) -> Mapping[int, List[int]]: @@ -74,6 +75,14 @@ def set_flag_liveness( self, liveness: Dict[str, Dict[str, List[str]]]) -> None: self._flag_liveness = liveness + @property + def register_liveness(self) -> Mapping[str, Dict[str, List[str]]]: + return self._register_liveness + + def set_register_liveness( + self, liveness: Dict[str, Dict[str, List[str]]]) -> None: + self._register_liveness = liveness + def has_expression_mapping(self, exprid: int) -> bool: return exprid in self.expression_mapping @@ -125,6 +134,7 @@ def serialize(self) -> Mapping[str, Any]: result["flag-reaching-definitions"] = self.flag_reaching_definitions result["definitions-used"] = self.definitions_used result["flag-liveness"] = self.flag_liveness + result["register-liveness"] = self.register_liveness return result def deserialize(self, d: Dict[str, Any]) -> None: @@ -141,3 +151,4 @@ def deserialize(self, d: Dict[str, Any]) -> None: self._definitions_used = { int(i): v for (i, v) in d["definitions-used"].items()} self._flag_liveness = d.get("flag-liveness", {}) + self._register_liveness = d.get("register-liveness", {}) From 18827d3691b8a8f526f0064bd04b1d60c31ffb87 Mon Sep 17 00:00:00 2001 From: Dan Phung Date: Fri, 31 Jul 2026 23:43:56 -0700 Subject: [PATCH 3/3] ASTI: attach derived register-liveness during AST construction Compute register-liveness in mk_asts, alongside set_ast_provenance and the flag-liveness computation, so it flows through the same builder path as the other provenance facts. The computation is auxiliary and guarded so a failure cannot abort AST generation. --- chb/astinterface/ASTInterfaceFunction.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/chb/astinterface/ASTInterfaceFunction.py b/chb/astinterface/ASTInterfaceFunction.py index aacfc179..722c5a00 100644 --- a/chb/astinterface/ASTInterfaceFunction.py +++ b/chb/astinterface/ASTInterfaceFunction.py @@ -164,6 +164,7 @@ def mk_asts(self, support: CustomASTSupport) -> List[ASTStmt]: # transfer provenance data to the AST abstract syntaxtree self.astinterface.set_ast_provenance() self.set_flag_liveness() + self.set_register_liveness(support) self.set_invariants() self.set_return_sequences() @@ -263,6 +264,19 @@ def set_flag_liveness(self) -> None: "flag-liveness computation failed for %s: %s", self.function.faddr, str(e)) + def set_register_liveness(self, support: CustomASTSupport) -> None: + # Derive address-keyed GP-register liveness from the reaching-def facts + # and attach it to the provenance. This is auxiliary, so a failure here + # must not abort AST generation. + try: + liveness = ASTILiveness(self.function).register_liveness( + set(support.register_sizes.keys())) + self.astinterface.astree.provenance.set_register_liveness(liveness) + except Exception as e: + chklogger.logger.warning( + "register-liveness computation failed for %s: %s", + self.function.faddr, str(e)) + def set_invariants(self) -> None: invariants = self.function.invariants aexprs: Dict[str, Dict[str, Tuple[int, int, str]]] = {}