diff --git a/chb/app/CHVersion.py b/chb/app/CHVersion.py index 1825189c..8ee59334 100644 --- a/chb/app/CHVersion.py +++ b/chb/app/CHVersion.py @@ -1,3 +1,3 @@ -chbversion: str = "0.3.0-20260418" +chbversion: str = "0.3.0-20260527" minimum_required_chb_version = "0.6.0_20260407" diff --git a/chb/app/FunctionStackframe.py b/chb/app/FunctionStackframe.py index 41a3d2bc..4a01883a 100644 --- a/chb/app/FunctionStackframe.py +++ b/chb/app/FunctionStackframe.py @@ -154,6 +154,27 @@ def stackoffset_gap(self, offset: int) -> Optional[int]: else: return None + def stackoffset_zero_write_extent(self, offset: int) -> int: + """Returns the span from offset upward through consecutive zero-write-only size-4 slots. + + Walks upward (toward less negative offsets) from offset + 4, collecting + size-4 slots whose accesses are all zero writes. Stops at the first slot + that is absent, has size != 4, has no accesses, or has any non-zero-write + access. Returns the total span: (stopping offset) - offset. + """ + current = offset + 4 + while True: + slot = self.stackslot(current) + if slot is None or slot.size != 4: + break + accesses = self.accesses.get(current, []) + if not accesses: + break + if not all(acc.is_zero_write for (_, acc) in accesses): + break + current += 4 + return current - offset + @property def accesses(self) -> Dict[int, List[Tuple[str, "FnStackAccess"]]]: if self._accesses is None: diff --git a/chb/cmdline/reportcmds.py b/chb/cmdline/reportcmds.py index 91451d13..7b78a8c3 100644 --- a/chb/cmdline/reportcmds.py +++ b/chb/cmdline/reportcmds.py @@ -77,6 +77,7 @@ from chb.app.Function import Function from chb.app.FunctionStackframe import FunctionStackframe from chb.app.Instruction import Instruction + from chb.invariants.FnStackAccess import FnStackAccess from chb.invariants.XConstant import XIntConst from chb.mips.MIPSInstruction import MIPSInstruction from chb.models.BTerm import BTerm, BTermArithmetic @@ -1262,6 +1263,20 @@ def find_function_attribute(app: "AppAccess", dstarg_index: int, fname: str, ins return intermediate_attribute +def _has_blockwrite_and_zero_stores_only( + accesses: List[Tuple[str, "FnStackAccess"]]) -> bool: + """True if accesses contain a block-write and only zero-value stores otherwise.""" + has_blockwrite = False + for (_, acc) in accesses: + if acc.is_block_write: + has_blockwrite = True + elif acc.is_zero_write: + pass + else: + return False + return has_blockwrite + + def calculate_buffer_size(stackframe: "FunctionStackframe", stack_offset: int, instr: "Instruction") -> tuple[Optional[int], str]: @@ -1302,6 +1317,20 @@ def calculate_buffer_size(stackframe: "FunctionStackframe", + "; replacing it by the size derived from the stacklayout", str(instr), str(stack_offset)) sizeorigin = "stackframe-layout" + elif buffersize == 4: + slot_accesses = stackframe.accesses.get(stack_offset, []) + if _has_blockwrite_and_zero_stores_only(slot_accesses): + buffersize = stackframe.stackoffset_zero_write_extent(stack_offset) + if buffersize > 4: + chklogger.logger.info( + "Stackbuffer size for %s at offset %s: inferred %s " + + "from zero-initialized slot sequence", + str(instr), str(stack_offset), str(buffersize)) + sizeorigin = "zero-init-sequence" + else: + sizeorigin = "stackslot-access" + else: + sizeorigin = "stackslot-access" else: sizeorigin = "stackslot-access" diff --git a/chb/invariants/FnStackAccess.py b/chb/invariants/FnStackAccess.py index 9b0c1022..dc7d3ce8 100644 --- a/chb/invariants/FnStackAccess.py +++ b/chb/invariants/FnStackAccess.py @@ -90,6 +90,10 @@ def is_block_read(self) -> bool: def is_block_write(self) -> bool: return False + @property + def is_zero_write(self) -> bool: + return False + @varregistry.register_tag("rs", FnStackAccess) class FnStackRegisterSpill(FnStackAccess): @@ -251,6 +255,10 @@ def value(self) -> "XXpr": def is_store(self) -> bool: return True + @property + def is_zero_write(self) -> bool: + return self.value.is_int_constant and self.value.is_zero + def __str__(self) -> str: return ( "stack-store("