Repository navigation
Conversation
Decision D3 (morluto#7477), provisional: admit ELF32 soft-float (ilp32) RISC-V images through a lane that keeps the pinned RISCV:LE:32:default language and corrects the calling convention in the bridge. This commit is the contract only; admission is unchanged. - The domain owns the e_flags float-ABI vocabulary and parses .riscv.attributes and Tag_RISCV_arch into a declared, absent or unavailable ISA declaration. - The Ghidra profile owns the lane table (class and float ABI), lane ids built from fields, the ilp32-prototype-v1 policy and its __rea_ilp32 prototype, and the extensions Ghidra 12.1.4 decodes with p-code. - The session handshake accepts the bridge's calling-convention report. - docs/ghidra-riscv.md keeps the decision record and per-ABI policies for ilp32 and ilp32f; a test keeps it in step with the tables. - tests/conformance/c/riscv-float-abi.c is the source the policies were measured on.
ESP32-C2, C3, C5, C6, C61, H2 and H21 builds are ELF32 soft-float (ilp32) images, which the RISC-V lane refused at its double-float check. Ghidra 12.1.4's generic RV32 compiler specification models ilp32d, so its stock prototype would read float and double arguments from fa0-fa7. - Target resolution reads the one SHT_RISCV_ATTRIBUTES section through the same bounded ELF declaration reader as range validation, and records Tag_RISCV_arch as declared, absent or unavailable. - Admission selects a lane from the class and float-ABI table. The soft-float lane also requires an RV32I Tag_RISCV_arch that declares only extensions Ghidra decodes with p-code; the refusal names any others (Zcb, Zcmp, Zcmt, Zb*, xesp*). - Soft-float profiles commit float_abi, arch, riscv_calling_convention ilp32-prototype-v1 and the lane elf32-exec-le-soft-float-generic-v1. Double-float profiles are unchanged. - ReaGhidraRiscvCallingConvention.java runs after analysis. It derives __rea_ilp32 from the default prototype without floating-point entries and rules, with fs0-fs11 killed by calls, self-checks its storage and assigns it to every non-thunk function, external function and function definition. Its report is written last. - The provider client requires that report exactly when the profile commits the policy, before any operation, and keeps it as session limitations and caller-project history. ELF32 single-float (ilp32f), quad-float and ELF64 soft- or single-float images stay refused, now with a reason built from the lane table.
verify:ghidra:riscv now also builds tests/conformance/c/riscv-float-abi.c with ESP32-C3's -march=rv32imc_zicsr_zifencei -mabi=ilp32, typed with DWARF and linked again with --strip-all. Every allocated section of the stripped image must keep its address and bytes. - riscv-checks.mjs holds the ilp32 storage of each fixture function together with the words the compiler's call site writes there. An LLVM objdump reading of the caller must find every word at its stated location: an unaligned named double, the a7/stack split, two floats in an integer pair, a large structure by reference, aligned variadic pairs and an indirect call. - Through CLI and stdio MCP, the soft-float builds must report the soft-float lane and ilp32-prototype-v1, __rea_ilp32 with exactly that storage (one integer word per location when stripped), caller pseudocode that passes the source constants and names no floating-point register, the prototype outcome in retained Evidence, and decoded fence.i and csrr. - The RV32 double-float image relabelled as soft-float, refused before, is admitted through the new lane. RV64 soft-float, single-float and unsupported bits are refused with their reasons. - riscv-checks.test.ts runs the checks offline against recorded LLVM and Ghidra output, including Ghidra's stock ilp32d prototypes as a negative control.
The psABI passes any value wider than 2 x XLEN by reference, scalars included. __rea_ilp32 converted only structures and unions over 8 bytes to pointers, so a long double (16 bytes) was joined into a1-a4. Measured on Ghidra 12.1.4, rea_riscv_abi_quad(long double value, int sel) read value from a4:4,a3:4,a2:4,a1:4 and sel from a5, where clang passes a pointer in a1 and sel in a2. - ReaGhidraRiscvCallingConvention.java now has one derivation driven by a per-policy row (floating-point entry size, preserved floating-point registers). Every policy inserts a rule before the input list's final join that converts any value of 9 bytes or more to a pointer; a return that size already becomes a caller buffer. The self-check requires a 16-byte value to pass by reference. - The fixture gains rea_riscv_abi_quad. The call-site oracle now checks the words the by-reference pointers point at, for the large structure and the long double, and the recorded data comes from an LLVM 19 build. - docs/ghidra-riscv.md: the decision record and the ilp32 policy row. ilp32-prototype-v1 keeps its id: no release carries it yet.
Decision D3 (morluto#7477), provisional, gains its ilp32f row: ELF32 images that declare the single-float ABI are admitted through the lane elf32-exec-le-single-float-generic-v1 with policy ilp32f-prototype-v1 and the __rea_ilp32f prototype, keeping the pinned RISCV:LE:32:default language. - The lane table gains the row, and admission requires the F extension in Tag_RISCV_arch beside the decoded-extension check. - docs/ghidra-riscv.md records how the one-float-one-integer rule is realised: the shared derivation's row limits float entries to 4 bytes, keeps fs0-fs11 preserved and adds Ghidra 12.1.4's join_dual_class with fillalternate for structures of up to 8 bytes. The prototype is the only thing that assigns storage: a check of every typed function and function-pointer type against the psABI refuses the image when one uses a shape the prototype misplaces (a float with two shorts, a pointer or a union, _Float16, packed or over-aligned float structures). float _Complex, which Ghidra imports as a qword, stays unmodelled. Custom storage for typed functions and an unverified-signature admission are recorded alternatives. - The session report for ilp32f can carry the bridge's refusal instead of the outcome; the handshake turns it into the user-facing reason. - tests/conformance/c/riscv-float-abi.c gains single-float-only shapes and a refused variant; the ilp32 build's code is unchanged. Until the bridge implements the policy (next commit), its post-analysis script rejects the new policy id and REA refuses ilp32f sessions.
ReaGhidraRiscvCallingConvention.java gains ilp32f-prototype-v1 as a row of its one derivation: float entries limited to 4 bytes, fs0-fs11 preserved, and structures that Ghidra's dual-class join splits between register files. The ilp32 row and its checks are unchanged. - For that row the derivation inserts, before the input and output lists' final join, a rule giving structures of up to 8 bytes join_dual_class with fillalternate. The shared rule still passes values of 9 bytes or more by reference. - The self-check also pins the storage of a float, double, two-float and float-and-int signature, and requires fs0-fs11 preserved. - After assigning the prototype to every function and function definition, the script compares the storage class it gives each signature type, as a first argument or return, with the psABI's from the flattened fields. It checks every typed function and every definition a pointer type refers to. When any signature uses a shape the prototype misplaces, the report carries a refusal naming up to eight signatures instead of the outcome, and REA refuses the session with that reason. The launcher test covers both policy ids.
verify:ghidra:riscv now also builds tests/conformance/c/riscv-float-abi.c with ESP32-H4's -march=rv32imafc_zicsr_zifencei -mabi=ilp32f, typed with DWARF and linked again with --strip-all, and a typed build with REA_RISCV_ABI_UNMODELLED that the bridge must refuse. - riscv-checks.mjs holds per-ABI tables: the ilp32 and ilp32f storage of each fixture function with the words its caller's call site writes there (and, for by-reference values, the words the pointer points at), the callers, and the indirect calls. Stripped builds record where Ghidra's inference differs from one word per typed location. - The objdump call-site oracle now follows fa registers (flw, fsw, fmv.w.x) and tracks stack memory per byte, so word loads see byte and halfword stores, across both callers. - Through CLI and stdio MCP the single-float builds must report the lane, ilp32f-prototype-v1 and __rea_ilp32f with the table's storage, including float-and-integer structures both ways round, float registers running out, long double by reference and an indirect float-and-integer call. - riscv-isa-probe.S adds fmv.w.x and fmv.x.w when the build has F. - The unmodelled build opens, then its first analysis call over MCP and the CLI fails with the bridge's reason naming its three signatures: a function passing a three-field structure, a _Float16 function and a function-pointer type passing the three-field structure. - Header mutations: RV32 double-float relabelled single-float is admitted; the soft-float image relabelled single-float lacks F and is refused; RV64 lp64f and high bits are refused.
The limitation tests froze a full explanatory sentence, the note count and array positions, so a harmless rewording failed them: the pattern Greptile flagged on morluto#7486. Assert the facts the notes must carry instead: the pinned language, the static-linkage boundary, one note naming the declared ABI, the ABI the pinned language models and the prototype REA applies, and the declared ISA.
Like the ilp32 tests, the ilp32f test froze its explanatory sentence, the note count and array positions. Assert one note naming the declared ilp32f ABI, the modelled ilp32d and the applied `__rea_ilp32f` prototype, and the declared ISA.
Takes main d48e472, which includes the squash merges of morluto#7481-morluto#7485. Conflict, resolved: GhidraRiscvProfile.test.ts imports this branch's lane and policy tables plus ghidraRiscvUnsupportedReason, which morluto#7484's refusal-table assertions use; embedded-integration holds the same list. morluto#7484's readback test merges cleanly and appears once.
The stripped branch of assertRiscvIlp32Storage compared parameter storage only, so an inferred return that lost the high word of a register pair, such as rea_riscv_abi_wide at a0:4 instead of a1:4,a0:4, still passed. Stripped returns must now use the typed build's storage, or none when the result goes through caller memory. Recovered type names are still not compared: a test accepts other type names and refuses the dropped high word.
Takes ilp32 d02fc6b: main d48e472 and the check of stripped return storage. Conflicts, resolved by keeping both rules: - riscv-checks.mjs: stripped functions get one inferred word per parameter location and the typed return storage (none for caller-memory results), unless the table records what Ghidra infers instead. The table's `stripped` is now { return, parameters }; the recorded ilp32f output gives tagged a0:4 and flagged a0:1, which leave their float in fa0. - docs/ghidra-riscv.md: ilp32f's text for both builds, with the stripped return storage. Semantic fixes: the new return-storage message names the function, not the ABI; the ilp32 test of it uses assertRiscvStorage; an ilp32f test refuses a stripped pair return that loses its high word, through a helper that keeps the ilp32f block under oxlint's 150-line limit.
Takes ilp32 d8ae325, which records decision D3 as decided in morluto#7477. The merge is clean.
e6d0618 (morluto#8228) is the parent of morluto#8229's squash e669365, which the next merge records. The merge is clean.
Recorded with -s ours: this branch already holds morluto#8229's final head d8ae325, and git patch-id --stable of the squash equals that of d8ae325's own patch (7bc824d8360616b7).
The merge is clean. Main's changes to GhidraRiscvProfile.test.ts (morluto#8295), GhidraProviderClient.ts and docs/testing.md touch other regions than this branch.
[Medium impact] The changes affect Ghidra's RISC-V analysis workflow and its calling-convention handshake. The PR appears safe to merge, with two non-blocking improvements to the new startup scan. FindingsFix with agent prompt### Issue 1
bridge/ghidra/ReaGhidraRiscvCallingConvention.java:438-440
If an imported array contains empty `Structure` elements, `flatten` revisits every element without adding any fields. The two-field cutoff never stops that case, so a large array count can cause millions of identical visits during startup. Classify the element type once and combine its field count with the array length instead of expanding every element.
### Issue 2
bridge/ghidra/ReaGhidraRiscvCallingConvention.java:346
The eight-example limit applies only after `refused` stores a full description of every rejected signature. Large typed images with repeated unsupported types or long C++ names therefore retain descriptions that will never be shown. Keep the totals separately and collect at most eight already-shortened examples while scanning.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.SummaryAdds an ELF32 single-float lane using
Diagram%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["ELF32 declares single-float ABI"] --> B{"F and supported extensions declared?"}
B -->|No| C["Refuse before provider startup"]
B -->|Yes| D["Install __rea_ilp32f"]
D --> E["Compare typed signatures"]
E -->|Mismatch| F["Report refused signatures"]
F --> G["Stop before serving analysis"]
E -->|Match| H["Report applied policy and counts"]
H --> I["Serve analysis"]
Reviews (1) · Last reviewed commit: "Merge upstream main 6e9ee97a6 into feat-..." · Reviewed by Greptile |
| if (type instanceof Array array) { | ||
| for (int index = 0; index < array.getNumElements(); index += 1) | ||
| if (!flatten(array.getDataType(), fields) || fields.size() > 2) return false; |
There was a problem hiding this comment.
If an imported array contains empty Structure elements, flatten revisits every element without adding any fields. The two-field cutoff never stops that case, so a large array count can cause millions of identical visits during startup. Classify the element type once and combine its field count with the array length instead of expanding every element.
Prompt To Fix With AI
This is a comment left during a code review.
Path: bridge/ghidra/ReaGhidraRiscvCallingConvention.java
Line: 438-440
Comment:
**Empty arrays slow startup**
If an imported array contains empty `Structure` elements, `flatten` revisits every element without adding any fields. The two-field cutoff never stops that case, so a large array count can cause millions of identical visits during startup. Classify the element type once and combine its field count with the array length instead of expanding every element.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.| misplaced.add(parameter.getFormalDataType().getName()); | ||
| if (misplaced.isEmpty()) continue; | ||
| functions += 1; | ||
| refused.add(function.getName(true) + " (" + String.join(", ", misplaced) + ")"); |
There was a problem hiding this comment.
Discarded descriptions consume memory
The eight-example limit applies only after refused stores a full description of every rejected signature. Large typed images with repeated unsupported types or long C++ names therefore retain descriptions that will never be shown. Keep the totals separately and collect at most eight already-shortened examples while scanning.
Prompt To Fix With AI
This is a comment left during a code review.
Path: bridge/ghidra/ReaGhidraRiscvCallingConvention.java
Line: 346
Comment:
**Discarded descriptions consume memory**
The eight-example limit applies only after `refused` stores a full description of every rejected signature. Large typed images with repeated unsupported types or long C++ names therefore retain descriptions that will never be shown. Keep the totals separately and collect at most eight already-shortened examples while scanning.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ba174afe69
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (component.getLength() == 0) continue; | ||
| if (component.getDataType() instanceof BitFieldDataType bitField) { | ||
| if (bitField.getBitSize() == 0) continue; | ||
| if (bitField.getBaseDataType().getLength() > 4) return false; |
There was a problem hiding this comment.
Use bit-field width when checking the XLEN limit
For a typed ilp32f parameter or return such as struct { float f; unsigned long long b : 1; }, the 1-bit field satisfies the XLEN limit even though its declared base type occupies 8 bytes. Local Clang 17 lowers this parameter to float, i32, using fa0 and a0. This check instead makes psabiKind report INTEGER, so misplacedSignatures rejects a correct FLOAT_INTEGER placement and refuses the entire image. Bound the field using getBitSize() rather than its base datatype’s length, and cover this shape in the ABI regression fixture.
AGENTS.md reference: AGENTS.md:L84-L84
Useful? React with 👍 / 👎.
|
Disclosure: this PR's H4 evidence was measured while Ghidra 12.1.4 loaded ESP-IDF's |
Part of the embedded firmware roadmap #7955.
Summary
ESP32-P4, H4 and S31 builds use the single-float ABI ilp32f, which REA refused at the RISC-V lane's float-ABI check. Ghidra's RV32 compiler specification models ilp32d, which passes a
doublein a float register. Limiting its float entries to 4 bytes is not enough: measured on Ghidra 12.1.4, that still joinsstruct { float; int; }intoa1:a0, where ilp32f passes the float infa0and the integer ina0. With this PR, ELF32 images that declare ilp32f and the F extension open in a new lane with a derived__rea_ilp32fprototype, and every argument of the fixture's calls sits where LLVM put it.opens images whose
Tag_RISCV_archlists only extensions that Ghidra 12.1.4 decodes, such as builds with ESP-IDF 5.5's-marchfor the preview ESP32-H4,rv32imafc_zicsr_zifencei. Images that also declare B, Zcb/Zcmp/Zcmt or an Espressif vendor extension stay refused, and the refusal names those extensions. As of ESP-IDF v6.1, that includes every ESP32-P4 build (xesploopandxespv, plusxesppieon 5.5 before rev 3) and the 6.x builds for H4 (B andxespdsp) and S31 (xesploopandxespv). One prototype cannot express every ilp32f shape on Ghidra 12.1.4, so when an image's typed signatures use a shape__rea_ilp32fwould misplace, REA refuses its analysis and names up to eight signatures.Closes #7477. Refs #8229, #2063. Coordinates with #4811 (RISC-V PIE), which edits the same admission and profile functions.
Decision record: the ilp32f row of D3
D3 is decided on #7477; this row is the follow-up it names.
docs/ghidra-riscv.md#ilp32fkeeps the full policy.elf32-exec-le-single-float-generic-v1, policyilp32f-prototype-v1, prototype__rea_ilp32f, on the pinnedRISCV:LE:32:defaultlanguage withgcc. Admission also requiresfinTag_RISCV_arch.__rea_ilp32fis one more row of feat(ghidra): admit RISC-V ilp32 ELF with a derived calling convention #8229's derivation inReaGhidraRiscvCallingConvention.java. It limits the default float entries to 4 bytes, keepsfs0–fs11preserved and keeps the shared rule that passes values of 9 bytes or more by reference. One new rule gives structures of up to 8 bytes Ghidra 12.1.4'sjoin_dual_classwithfillalternate: each 4-byte part that holds onefloattakes a float register, and each other part an integer register.struct { float; short; short; }, afloatbeside a pointer or a union (LLVM passes these in integer registers), and it cannot place_Float16or a packed or over-aligned float structure. After assigning the prototype, the script compares the storage class it gives each signature type, as a first argument and as a return, with the psABI's, for every typed function and function-pointer type. If any differ, the bridge reports{policy, refused}with up to eight signatures, and REA refuses the session with that reason.join_dual_classwithout the check: those signatures would get wrong storage without notice.ilp32f-prototype-v1persist in sessions, snapshots and Evidence; a changed derivation ships as a new policy id that invalidates that lane's snapshots only. Verification:npm run verify:ghidra:riscv.Problem and expected behavior
e_flags0x3was refused: "…the single-float ABI (ilp32f) declared by e_flags has no verified calling-convention policy."__rea_ilp32f; without F they are refused before any provider starts. An image whose signatures__rea_ilp32fwould misplace opens, and its analysis is then refused with the signatures named. ilp32 and double-float behaviour and identity are unchanged.Change and scope
244c42306: the ilp32f row of the decision record and policy table (with the required F), the session-report schema for{policy, refused}, docs, and fixture source with single-float-only shapes and a refused variant. ilp32f images pass admission, but until the next commit the bridge rejects the new policy id, so REA still refuses their sessions.88c82b88a: the bridge row, its self-check (a float, double, two-float and float-and-int signature, andfs0–fs11preserved), and the comparison with the psABI.4f97b9202: the real lane: typed and stripped builds with ESP-IDF 5.5's ESP32-H4-march, a refused unmodelled build, per-ABI storage tables, a call-site oracle that followsfaregisters and per-byte stack memory, and header mutations.6940199b4: the ilp32f limitation tests check facts, not wording.51419dca5,5fd177413,688c1d567,2db1fa8a9);688c1d567takes feat(ghidra): admit RISC-V ilp32 ELF with a derived calling convention #8229's stripped return-storage check and records ilp32f's two stripped returns that differ (taggedata0:4,flaggedata0:1). Merges ofmainup to6e9ee97a6(8acebd1f4,a5c020aae,ba174afe6);a5c020aaerecords feat(ghidra): admit RISC-V ilp32 ELF with a derived calling convention #8229's squashe669365c3with-s ours, after checking that itsgit patch-id --stableequals that ofd8ae325f1's own patch.Not included: the shapes the check refuses (above); quad-float, RVE, TSO, ELF64 soft- or single-float,
ET_DYN(#4811); and images that declare undecoded extensions (above).Contract and boundary impact
src/domain/riscvIsaDeclaration.ts, the tables insrc/ghidra/GhidraRiscvProfile.ts,bridge/ghidra/ReaGhidraRiscvCallingConvention.java). The earliest changed stage is Ghidra admission: the lane row and its required F.userMessage, not only asdiagnostics.reason.riscv_elf.float_abi: "single",riscv_elf.archandriscv_calling_convention: ilp32f-prototype-v1; session Evidence states the prototype and its counts. ilp32 and double-float profiles are unchanged (tested).docs/public/product-catalog.json), package, or installation impact: none; the bridge source already ships.Expected effects:
Evidence and regression coverage
mainrefuses ELF32e_flags0x3("no verified calling-convention policy"). Measured on Ghidra 12.1.4 (recorded in feat(ghidra): admit RISC-V ilp32 ELF with a derived calling convention #8229's docs): 4-byte float entries alone putstruct { float; int; }ina1:a0. The conformance checks refuse the stock model's placements: a wholedoubleinfa0, and that structure joined into an integer pair.-march).float _Complexgets integer storage without notice, because Ghidra 12.1.4 imports it from DWARF as an untypedqword(the stock double-float lanes share this). With D hardware and the ilp32f ABI, Ghidra preserves all 64 bits offs0–fs11, where the psABI preserves the low 32.Per-file test coverage (counts on the head)
src/ghidra/GhidraRiscvProfile.test.ts(66 tests): the single-float lane row and where F is required; ilp32f admission and its refusals; the single-float profile's exact parameters; the stated ABI, prototype and ISA; and the launch with the single-float policy and its outcome.src/ghidra/GhidraSessionValues.test.ts(40 tests): the ilp32 and ilp32f outcomes and an ilp32f refusal accepted. Refused: an unknown policy, a custom-storage count, an ilp32 refusal, a refusal with counts, and an empty or unbounded refusal.tests/boundary/providers/ghidra/ghidraLauncher.test.ts(59 tests, 3 Windows-only skipped): the post-script for both policy ids.tests/conformance/ghidra/riscv-checks.test.ts(12 tests): per-ABI tables and the call-site oracle across both callers, withfaregisters, halfword stores and sub-word stack words. Also ilp32f storage, refusing a float-and-integer structure the wrong way round, the stock pair and a wholedoubleinfa0. Stripped storage with ilp32f's recorded exceptions, refusing a stripped pair return that loses its high word. Both callers' pseudocode, refusing an unaccounted register value.CLI output on this PR's head
For the single-float build of the fixture (
-march=rv32imafc_zicsr_zifencei -mabi=ilp32f), the committed profile:{"language_id":"RISCV:LE:32:default","compiler_spec_id":"gcc", "riscv_support_lane":"elf32-exec-le-single-float-generic-v1", "riscv_calling_convention":"ilp32f-prototype-v1", "riscv_elf":{"elf_class":32,"byte_order":"little","type":2,"flags":3, "float_abi_double":false,"rvc_declared":true,"rve_declared":false, "tso_declared":false,"float_abi":"single", "arch":"rv32i2p1_m2p0_a2p1_f2p2_c2p0_zicsr2p0_zifencei2p0_zmmul1p0"}}Excerpts of both callers' pseudocode:
The limitations include "RISC-V calling-convention outcome: 16 functions and 16 function definitions use __rea_ilp32f." The same source built with
REA_RISCV_ABI_UNMODELLEDopens, and its first analysis call fails:{"code":"execution_failure", "message":"RISC-V calling-convention policy ilp32f-prototype-v1 refused this image, so REA does not serve its analysis: 2 functions and 1 function-pointer type pass by-value types whose psABI storage __rea_ilp32f cannot express: rea_riscv_abi_spread (returns rea_riscv_abi_spread, rea_riscv_abi_spread); rea_riscv_abi_half (returns _Float16, _Float16); function-pointer type _func_void_rea_riscv_abi_spread_int (rea_riscv_abi_spread). See docs/ghidra-riscv.md."}For evidence-bearing changes:
Validation performed
Checks ran on the head
ba174afe6, which mergesmain6e9ee97a6.npm run check:fast,format:check,docs:check(built steps executed) andverify:module-boundaries— passed.npx knip— one finding, the unused exported typeDarwinHelperCache(src/process/DarwinHelperCache.ts:465). knip on a builtmain6e9ee97a6prints the same output, so this PR adds none.npm run test:focused --22 files: the 4 test files this PR changes, the other RISC-V test files, and tests of the code it shares withmain's latest changes — 948 passed, 4 skipped (Windows-only).npm ci --ignore-scripts), onba174afe6:npm run verify:ghidra:bridge-sources— passed: javac 21.0.12 compiles all 11 bridge sources against Ghidra's 195 jars.npm run verify:ghidra:riscv— passed for both ABIs:"mocked": false, cleanup complete, process lineage verified with no descendants. The typed ilp32f build has the compiler's storage at all 13 calls of both callers, the stripped builds return where the tables say, and the unmodelled build is refused with its three signatures named. Per fixture below.npm run verify:ghidra:mips— passed, run because this PR changes the shared handshake's failure message: all six fixtures, including the soft-float pair with__rea_soft_float; process lineage verified with no descendants.fast-uri3.1.8 andqs6.16.0 (lockfile: 3.1.9 and 6.16.1).Lane results per fixture on ba174af
0x1and0x3is admitted over MCP and the CLI through the soft- and single-float lanes; RV640x1(lp64),0x3(lp64f) and0x80000005are refused.-march): unchanged from feat(ghidra): admit RISC-V ilp32 ELF with a derived calling convention #8229. 13 functions, with 12 and 0 function definitions; the soft-float image relabelled0x3lacks F and is refused.-march=rv32imafc_zicsr_zifencei): the single-float lane andilp32f-prototype-v1.fence.i,csrr,fmv.w.xandfmv.x.wdecode. Everyinspect_native_apiprototype uses__rea_ilp32fwith the table's storage, for examplefa0:4,a0:4for the flagged structure,a0:4,fa0:4for the tagged one, and the integer paira1:4,a0:4for adouble. 16 functions and 16 function definitions use the prototype;0x80000003is refused.tagged,flaggedandfill. Returns are in the typed registers, excepttaggedata0:4andflaggedata0:1, which leave their float infa0. 16 functions and 0 function definitions.llvm-objdumpplaces every expected word for the 10 ilp32 calls and the 13 ilp32f calls, in integer and float registers and on the stack.Compatibility, safety, and release
Author readiness
Human owner:
Before merge
type(scope): outcome.