You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
🔴 Bug: Missing RISCVAEncoder import path validation — Line 19: If RISCVAEncoder raises on any valid-assembly edge case (e.g., .global, .section directives), the bench will false-fail. The broad exception list (IndexError, KeyError, TypeError, ValueError) doesn't catch AttributeError or custom exceptions the encoder might raise.
Suggestion: Catch Exception with a log, or check what exceptions the encoder's documented contract raises.
🟡 Regression: _validate_asm no longer detects unresolved vregs — The old code explicitly flagged v<digits> tokens that slipped past register allocation. The new encoder-based validation only confirms the assembly is syntactically valid — it won't tell you why it failed if the encoder errors out on a vreg. The error message loses diagnostic value.
Suggestion: If assembly fails, add a fallback check scanning for unresolved vregs before reporting.
🟡 Performance overhead: assemble called twice per run — The encoder is invoked once in _validate_asm and again in the actual codegen path (block_from_machine_instrs → emit). This doubles encoder work for validation, inflating benchmark times. If the benchmark measures sv_static_instrs via the same codegen, the validation assemble is wasted work.
🟡 stats["llvm_spill_slots"] and related keys may not exist when LLVM unavailable — The print uses if stats["llvm_available"]: guard for the first block, but the final "ScratchV regalloc" print block is outside the guard and looks safe — however, if LLVM code earlier sets these keys only when available, accessing them unconditionally could KeyError. Verify the key population logic.
💭 Unicode removal (✗/✓ → FAIL/PASS) is good for CI/logging compatibility, but consider PASS/FAIL prefixing on lines that print numeric stats too (currently no prefix on those lines) for grep-ability.
📁 benchmarks/test_regalloc/bench_dense.py
🔴 Breaking semantic change in "spills" key — Previously "spills" reported len(alloc._spill_slots) (number of spill slots allocated). Now it reports alloc.spill_store_count (number of spill store instructions). Any downstream consumer comparing across runs will see inconsistent values under the same key. If both are needed, give them distinct names.
🟡 Three keys share the same value — "spills", "spill_stores", and "reg_spill_count" all map to alloc.spill_store_count. This creates confusion about which key is canonical. Either consolidate to one, or document why each alias exists (e.g., backward compat). If they're there for report schema compatibility, add a comment.
🟡 Accessing private attribute alloc._spill_slots — "spill_slots": len(alloc._spill_slots) bypasses encapsulation. The other new fields (spill_stores, reloads) use public accessor properties. Prefer a public method/property for spill slot count to stay consistent with the rest of the changes.
💭 statistics.mean(times) will raise on empty list — If repeats=0, this crashes. Pre-existing issue, but now that you're touching this code it's a natural time to add a guard (e.g., times if times else [0]).
💭 Good simplification — Dropping the spill_counts list and inline ASM parsing in favor of centralized allocator state is a clean improvement. The final-run-only pattern also avoids unnecessary allocations in the loop.
📁 benchmarks/test_regalloc/bench_simple.py
🔴 Bug: Three keys map to the same value — Lines 78-80: "spills", "spill_stores", and "reg_spill_count" all equal alloc.spill_store_count. This is misleading — spills and reg_spill_count were different concepts in the old code (spill slots vs store operations). If they're intentionally the same, deduplicate; if not, fix the mapping.
🟡 Inconsistent access pattern — Line 79 accesses alloc._spill_slots (private underscore attribute) while line 80 uses alloc.spill_store_count (public). Suggest using a public property for spill slots too, e.g., alloc.spill_slot_count.
🟡 spill_slots vs spill_stores semantics unclear — len(alloc._spill_slots) (total spill slot capacity allocated) vs alloc.spill_store_count (actual store operations generated) could differ significantly in complex cases. Without a comment or a test asserting they sometimes differ, readers will assume they're interchangeable.
🟡 No stability check for new metrics — times is averaged across repeats runs, but all the new spill/pressure metrics are taken from a single final run. If any of these are non-deterministic (e.g., depend on object hash ordering), the reported values could vary. Consider averaging them like times or documenting why a single run suffices.
💭 Nit: _alloc exposure — The _alloc field exposes the full allocator, making all these new metrics trivially derivable by consumers. This reduces the value of pre-computing them in the dict. Consider whether _alloc should stay or the redundant keys should be removed.
🟡 Missing fallback for unsupported operand kinds — _emit_move assumes src.kind is either "imm" or a register kind. If a memory operand or other kind ever reaches this method (e.g., via a future codegen path), the else branch will emit MV with an invalid source, producing an illegal instruction silently.
🟡 All 8 call sites replaced unconditionally — The replacement is correct (MV with an immediate source is never valid), but consider whether MachineOp.LI should have the comment parameter in a different position. Currently LI is emitted as _emit(MachineOp.LI, dst, src, comment=comment), which places src (an immediate) in the src1 position. Verify that MachineInstr semantics for LI expect the immediate in src1 rather than requiring special handling.
💭 Naming — _emit_move is good, but _emit_copy might read more naturally for a pseudo-instruction that "copies" a value regardless of source type. Minor.
💭 Docstring accuracy — "for either a register or an immediate" should also mention the fallback behavior (or lack thereof) to guide future contributors.
📁 scratchv/backend/machine_semantics.py
🟡 Bug risk: virtual_register_defs_uses string formatting may not match allocator expectations — Line 249: str(operand.value) extracts just the name, while linear_scan_operands (line 265) uses str(operand).lstrip("%"). If these produce different strings (e.g. one includes a % prefix or type suffix, the other doesn't), the allocator and emitter will track different names for the same virtual register, silently breaking spill/reload decisions.
Suggestion: Use a shared helper (e.g. vreg_name(operand)) in both functions to guarantee consistency.
🟡 Inconsistent return type from linear_scan_operands — Lines 273–278: When target_from_comment is False, the function returns instr.comment as-is, which could be None. When True, it normalizes to "". Callers must defensively check for None in one code path but not the other.
Suggestion: Always return str — e.g. comment = instr.comment or "" at the top, or document the contract explicitly.
🟡 virtual_register_defs_uses can silently include immediates as virtual registers — Line 246: The _names_at check uses operand.kind == "vreg", but immediate_positions are not consulted. If an immediate happens to carry a .kind of "vreg" (e.g. from a misconfigured intermediate IR), it would be treated as a live virtual register. This is unlikely but the check is fragile — consider explicitly skipping positions in immediate_positions to make the invariant self-documenting.
💭 Repeated standalone MachineOpSemantics(...) calls for common layouts — FABS_D, FNEG_D, FMV_S, FMV_S_X, LI_D all define near-identical semantics inline. A small constant set (_DEF_USE_PSEUDO, _STORE_PSEUDO, etc.) would reduce drift risk — a future edit to FMV_S might miss FNEG_D.
💭 _MISSING_SEMANTICS raises at import time — This is a good defensive check. Consider adding a similar assertion that no MachineOp in OP_SEM has contradictory flags (e.g. is_label + is_terminator, or defs referencing positions outside [0,1,2]). A quick validation loop would catch typos at module load.
📁 scratchv/backend/regalloc_cfg.py
🟡 Fragile target extraction — Line 64 (inst.operands[-1]): Assumes the branch/jump target is always the last operand. If instruction formats ever place the target elsewhere or add trailing annotations, this silently returns wrong targets. Consider a dedicated method or field on the instruction instead.
🟡 apply_cfg_liveness mutates intervals in place — Lines 156–160 directly modify interval.start and interval.end. Callers that expected immutability will get surprising side effects, and the sorted() return doesn't restore original values on failure paths. Consider constructing new interval objects instead.
🟡 Block uses set doesn't account for intra-block kill — Line 105: block.uses |= inst.uses - block.defines correctly excludes uses shadowed by earlier defines, but a use followed by a re-define in the same block still appears in block.uses (since block.defines is empty when the use is processed). This is overly conservative but not incorrect — just note that block.uses is an upper bound on true live-in.
🟡 No cycle detection for backward edges — The iterative liveness loop processes reversed(blocks) but never checks for cycles in the CFG. A back-edge (e.g., from a loop condition) converges fine due to the changed loop, but there's no termination guarantee if block.successors ever references a name not in by_name (e.g., a stale reference from a refactored code path). Adding a max-iteration cap would make this defensive.
💭 slots=True on both dataclasses — These instances are numerous in allocation passes; slots=True reduces per-instance memory overhead significantly with no readability cost.
💭 instruction_to_block maps on inst.id — If two instructions share an id (or id is not guaranteed unique), the dict silently overwrites. A debug assertion in the loop would catch this early.
⚠️ 未审查的文件
scratchv/backend/regalloc_linear.py
scratchv/backend/regalloc_linear_v1_5.py
scratchv/backend/regalloc_metrics.py
scratchv/backend/regalloc_rewrite.py
scratchv/backend/register_alloc.py
scratchv/backend/riscv_encoder.py
scratchv/simulator/tinyfive.py
tests/test_regalloc_metrics.py
tests/test_regalloc_p1.py
tests/test_regalloc_pseudo.py
tests/test_simulator.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
mv,li,max,bnez,j, localcall, labels)Correctness fixes
maxlabels and user labelsValidation
liboundaries, max aliasing, branches, CFG paths, and spill reloadsCurrent boundaries
maxaccepts a register RHS or immediate zerocallsupports local JAL-range targets; external/far relocation is not implemented