Repository navigation
[AUTOMATED] Preserve SPARC32 __divdi3 quotient results - #1252
Conversation
Seed the reserved runtime helper with its signed eight-byte DI-mode operands and result under libproto on the evidenced SPARC32 ELF ABI. Reuse code-import resolution and ambiguity filtering. Keep unknown external-call inference unchanged and retain explicit prototype precedence. Exercise low-word quotient stores and a scalar override by compiling and executing independently authored callers.
|
[AUTOMATED] This turns correct output into wrong output for the most common printf 'void g(void);\nlong long q(long long a,long long b){return a/b;}\nlong long keep(long long a,long long b){long long r=a/b;g();return r;}\nlong long guard(long long a,long long b){if(b==0)return -1;return a/b;}\n' > r.c
clang -target sparc-linux-gnu -integrated-as -O2 -c r.c -o r.o && kuna decompile-all r.oOn main, The cause: the callee's locked 8-byte result gives the caller only a single o0 return trial ( |
|
[AUTOMATED] Addressed @mahaloz's report in 8b922af. The runtime contract stays enabled under
The regression exercises All three |
|
[AUTOMATED] Follow-up 1f56c35 removes 13 assertions requiring dropped or truncated ARM float values with recovery disabled: the seven reported parity failures and six related broken-output pins. Their obsolete baseline entries and unused script commands were removed, not re-recorded against different broken output. The 31 retained assertions pass with a freshly rebuilt stage runner, including enabled double returns, ABI guards, scalar results, NaN payloads and explicit helper contracts. Strict specification checks also pass. No production code changed after 8b922af, whose full workspace suite passed in CI. Monitoring replacement CI for the final head. |
|
[AUTOMATED] Final head 42d98c3 passed both upstream checks: parity gates and the full cargo workspace suite. CI: https://github.com/Noelo-Lab/kuna/actions/runs/39881775266 . The reported caller-return regression is addressed by 8b922af; 1f56c35 removes the obsolete broken-output pins. Local verification was focused tests and CLI smoke runs only; full suites ran in CI. |
|
[AUTOMATED] Reviewed the SPARC32 Findings and fixesAt the previous PR head, an optimized function choosing between the quotient and The fix keeps proof tied to the corresponding words of the same declared eight-byte integer call result, while following literal expressions and the relevant phi paths. It preserves pending frame reads until stack SSA can resolve them. The underlying frame-address consumer now recognizes an OR displacement only when existing known-bit information proves that OR equals addition; unknown or overlapping masks remain rejected. No helper-name exception, guessed alignment, increased fixed heritage-pass count, or debug hook was added. Explicit caller/callee contracts and scalar override precedence remain intact. Documentation and behavioral regressions were updated. There is an unavoidable inference boundary: on a high-word-first ABI, a whole wide return and a high-word scalar return can have identical machine bodies when the unused word is simply left live. An authored PowerPC32 control demonstrated this byte-for-byte; explicit scalar caller contracts retain the scalar result. This is not a claim to recover unavailable source signatures universally. Focused checksExecuted on the merged head:
ARM assertion auditI did not treat removal of the 13 broken-output pins as proof of correctness. The full benchmark changes six default-mode outputs: The actual ELF DWARF declares eight-byte Broad regression and quality resultsCompared the frozen merged head against current-main production code/specs, with matching default options, dataset, metric implementation and compiler configuration:
GED and type-match values are unchanged function-by-function. Byte compilation succeeds for 78,639 targets and fails for the same 16,558 in each arm. There are 77 numerical byte-score differences under the official one-second diff deadline. For all 77, separate diagnostics verified identical original/recompiled bytes and normalized assembly across arms, and identical scores with only the diff deadline disabled. Official default scores were preserved, not replaced by diagnostic scores. The dataset contains no SPARC targets; the authored SPARC regressions therefore provide the direct behavior coverage. The six changed ARM C outputs are disclosed above rather than hidden behind unchanged aggregate metrics. Engine-only CPUUsed independently compiled persistent base/head workers on one pinned-core allocation, the same 81-entry cohort across 10 architectures (76 real-ELF functions plus five authored SPARC cases), two warmups per variant and seven measured samples each, alternating variant order.
This is a bounded-cohort correctness/performance observation, not a universal speed or equivalence claim. The previous head's successful upstream CI was not needlessly repeated as a complete workspace run. Fresh CI for the pushed integration head |
guard_returns_overlapping registered every contained output entry on every model. On ARM little-endian the extra entry (s1 under an 8-byte d0, r1 under r0:r1) stays inactive because whole_pair never applies there, and voidret's whole-or-none rule then drops the return entirely: betaflight's hard-float fabs/floor-style routines became `void` with their bodies dead-code eliminated. Register the extra entries only when kuna_bejoin's wide_pair says the model returns its pair high word first; every other model keeps upstream's biggest-contained guard. This restores base output on ARM, so the 13 deleted option-off assertions in kuna-arm-float-return.xml and kuna-armfloat-preset.xml (and their baseline entries) come back unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
…ult off The seeded `long long __divdi3(long long, long long)` fixes a caller that stores only the low word, but a caller that returns arithmetic on the quotient (`a / b + 1`, `-(a / b)`, `f ? a / b : c`) keeps only the high word or returns nothing, where main returns every word: the whole-pair proof accepts bare slices and literals only. Move the seed out of `libproto` into its own analysis pass and option, `libgccsigs`, off by default, so the default output is main's. The generic BE pair-return work stays on. - kuna_libgcc.rs: LibgccSigsPass (id `libgccsigs`), committed only when on. - phases.toml row, dispatch, Architecture flag, analysis_pass_enabled, console live value; catalog counts 339 -> 340 (analysis 89 -> 90), phase_catalog.json, docs/options.md, kuna-catalog.xml Noelo-Lab#15 and its baseline-stages entry. - libgcc_divdi3 tests pass `--option libgccsigs on`; the store-low-word test also checks that the default equals `off` and keeps main's four-word call. - Spec: the seed is opt-in and why (01), the pair proof's arithmetic limit (04). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
… one entry (Noelo-Lab#1260) A switch whose index a call writes through a pointer keeps only one case. Its other indexes fall through to the code after the switch. The example below is PPC64: a stack slot is set to 0, a helper writes the switch index through its address, and a five-entry table dispatches on it. Each case returns 40–43, and entries 0 and 4 share a body. ```sh cat > select.s <<'EOF' .abiversion 2 .globl select .type select,@function select: .long 0x7c0802a6,0xf8010010,0xf821ff71,0x39200000,0x38a10070,0x91210070,0x48000091 .long 0x2f83000b,0x419e0074,0x81210070,0x2b890004,0x419d0060,0x7d2a4b78,0x429f0005 .long 0x7d0802a6,0x3d080000,0x39080020,0x794a1764,0x7d2852aa,0x7d294214,0x7d2903a6 .long 0x4e800420,0x00000014,0x0000001c,0x00000024,0x0000002c,0x00000014,0x38600028 .long 0x48000028,0x38600029,0x48000020,0x3860002a,0x48000018,0x3860002b,0x48000010 .long 0x38600063,0x48000008,0x38600001,0x38210090,0xe8010010,0x7c0803a6,0x4e800020 .long 0x90850000,0x38600000,0x4e800020 EOF powerpc-linux-gnu-as -a64 select.s -o select.o kuna decompile select.o select ``` ```c if ((unsigned long)v1[0] <= 4) { switch(*(int *)((unsigned long)v1[0] * 4 + 0x400058) + 0x400058L) { case 0x40006c: return 0x28; } } return 99; ``` Ghidra prints `case 0: case 4: … case 1: … case 2: … case 3:` for the same code. ## Fix - During flow, the table is recovered on a partial copy of the function. That copy doesn't yet know the call takes the slot's address, so it reads back the stored 0 and builds a one-entry table. The full analysis later finds five entries, but the switch has already been built with one out-edge. - Port upstream's multistage restart. When the flow-time table has one address and the late model has more, `matchModel` records a multistage hint for the jump and restarts the function. The next flow re-queues the table through `checkForMultistage`/`recoverMultistage`. - The second stage runs on the same kind of partial copy, so it can fold the index again. The hint therefore also carries the late model's targets, and a second stage that again finds one address uses them. - A jump restarts at most once, and only when every value in the late model's range passes the guards on its path and every target is mapped and within the `0xffff` reach `sanityCheck` allows. A model wider than its guards would otherwise turn rows a lower bound excludes into cases. - When the restart budget runs out, the last pass (which still labels its table) is printed with an `Exceeded maximum restarts with more pending` warning. ## Tests - `kuna-cli` `ppc64_delayed_constant_switch`, each big- and little-endian: - the function above keeps `case 0: case 4:`, `case 1:` to `case 3:` and `default:`; - a `5 <= i && i <= 9` guard over a table whose rows 0–4 also hold mapped targets keeps the lower bound, and prints none of those rows as cases; - a chain of nine such switches keeps a structured body under the restartlimit. The first fails without the fix. The other two fail if the restart skips the guard check or re-follows flow at the limit. - No datatest or stage expectation moved, and a sweep of 478 ELF fixtures changes no function. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
…lo-Lab#1287) A flag-writing or register-writing SPARC delay slot could decide the branch it sits under. This example should clear a byte only when both arguments are below the limit: ```sh python3 -c "from pathlib import Path; Path('/tmp/sparc-delay.bin').write_bytes(bytes.fromhex('80a2000a0a80000580a2400a81c3e00801000000010000002abffffdc02ac00081c3e00801000000'))" kuna decompile /tmp/sparc-delay.bin 0x1000 --raw-image \ --target sparc:BE:32:default --base 0x1000 --assert-strict \ --assert 'prototype 0x1000 void sub_1000(unsigned int previous, unsigned int current, unsigned int limit, unsigned char *out)' ``` Before the fix the body lost the first comparison (`if (current < limit) *out = 0;`); it now keeps the `limit <= previous` early return. ## Fix - The icc/xcc `cs`, `neg` and `vs` conditions exported the live flag register, so a delay slot that set the flags decided the branch. They are now copied before `delayslot(1)`, like the neighboring condition operands. - The non-annulled register branches (`brz`/`brlez`/`brlz`/`brnz`/`brgz`/`brgez`) tested `rs1` after expanding the delay slot, so `brz,pt %o0,L; mov 5,%o0` decompiled to `return 5;`. The register test is now taken before the slot. gcc -O2 sparc64 emits this shape far more often than the flag case (zlib `deflate`/`deflateInit2_` and sqlite3 `sqlite3MutexInit` regain lost NULL and callback checks). - Annulled forms, FBfcc/FBPfcc and `jmpl` were already correct. No option: SLEIGH correctness repair. ## Tests - kuna-sleigh emulator tests for the flag and register-branch cases (fail on the old spec), and a `tests/stages/` testcase. - SPARC compiler digests and the gh6990-returnpair lift fixture refreshed (unique-space offsets only). - 150 SPARC objects (gcc-12/clang sparc and sparcv9, zlib, sqlite3): 194 functions change, all gcc -O2 delay-slot sites; non-SPARC output is byte-identical; speed within noise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
`aggressive_carries_every_default_off_option` requires a default-off option to be in the preset or listed with a reason. libgccsigs must not reach the default path: a SPARC32 caller returning arithmetic on the quotient would lose words. List it in EXCLUDED_ON_PURPOSE and in docs/modes.md. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
|
[AUTOMATED] The SPARC32 printf '%s\n' 'long long inc(long long a,long long b){return a/b+1;}' 'long long neg(long long a,long long b){return -(a/b);}' 'long long sel(long long a,long long b,long long c,int f){return f?a/b:c;}' > di2.c
clang -target sparc-linux-gnu -integrated-as -O2 -c di2.c -o di2.o
kuna decompile-all di2.o --option libgccsigs onWith the contract, Pushed on top of ed210c2:
The big-endian pair-return work stays on by default. With the option off, a 752-input corpus diff against main changes only MIPS BE DWARF wide returns (now returned whole, in ABI order) and SPARC -O0 frame reads through |
|
[AUTOMATED] Investigated whether |
|
@mahaloz do you prefer to merge this as-is and I open a new PR to work on the general solution, or do you prefer to turn this PR into the general solution? I personally think having a default-off feature which potentially generates wrong code is not so useful, so it'd rather go with the second option. |
whole_pair took a declared eight-byte call result as the caller's return
whenever both words still reached the RETURN. On PowerPC32, MIPS and ARM
big-endian the result stays in the return registers after the caller uses
it, so `void store(long long a,long long *p){*p=f(a);}`,
`void global(long long a){g64=f(a);}` and `if (f(a) > 100) g();` became
`long long` functions returning the stored value, where main prints `void`.
Require every reader of the call result, through moves, slices and phis, to
be a RETURN or an input a later call's parameter recovery has not made an
argument (upstream's onlyOpUse/checkCallDoubleUse rule; the little-endian
models already behave this way).
Also split contained return guards only when the high-first pair has
four-byte words, the only shape whole_pair can complete; a 64-bit BE model
keeps upstream's single guard.
The libgccsigs row now counts the SPARC clamp shape among the callers that
lose words (14 of 21). New test: a_consumed_wide_result_is_not_a_return
(ARM BE, -O0/-O2).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
|
[AUTOMATED] Pushed 5ee4669: a declared wide call result is no longer read as the caller's return when the caller also consumes it. printf 'long long f(long long);long long g64;void store(long long a,long long *p){*p=f(a);}void global(long long a){g64=f(a);}\n' > c.c
clang -target armeb-linux-gnueabi -fno-pic -O2 -c c.c
kuna decompile c.o store --assert "prototype f long long f(long long a)"main prints |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013BBaWTixHX2E5cBwneY5aW
Preserve SPARC32
__divdi3results both when a caller stores only the low word and when it returns the full quotient.libprotooption, restricted to SPARC32 big-endian ELF with the default convention. Explicit prototypes retain precedence.Independent caller-return reproduction:
q,keepandguardretain the full 64-bit quotient;keeppreserves the intervening call andguardreturns-1without dividing when its divisor is zero.Focused verification:
libgcc_divdi3: generated C compiled and executed with GCC and Clang at-O0/-O2, with undefined-behavior sanitization. Covers the low-word store, explicit scalar override, direct/saved/guarded full returns, scalar truncations and call effects. Caller-return inputs are compiled for SPARC at-O0and-O2, using either the default runtime contract or an explicit wide prototype withlibprotodisabled.call_result_pair_returns: neighboring ARM, MIPS and i386 return-pair regressions.python3 tools/check_spec.py --strict.Full suites are left to CI.