From cad19f243aa4273fa9dfdc58c9a8e5a842c5219d Mon Sep 17 00:00:00 2001 From: MatyMeatBoy <108221324+MatyMeatBoy@users.noreply.github.com> Date: Wed, 4 Nov 2026 19:25:23 -0300 Subject: [PATCH] Add opt-in range proof for Thumb byte-loop decrement flags Prove a byte seed, one counter decrement and a guarded repeat edge before reusing signed SUB flags. Audit outside entries, counter changes and both-path carry liveness within fixed bounds. Add runtime and exclusion fixtures without changing production module flags. Co-Authored-By: Codex --- .github/workflows/check.yml | 1 + Makefile | 2 +- tools/install_agbcc_sdk_cc.sh | 6 +- tools/patches/README.sdk-cc-reuse.md | 35 +++++ tools/patches/agbcc-sdk-loop-cc-reuse.patch | 166 ++++++++++++++++++++ tools/scripts/test_sdk_loop_cc_reuse.py | 83 ++++++++++ tools/tests/fixtures/sdk_loop_cc_reuse.c | 47 ++++++ 7 files changed, 338 insertions(+), 2 deletions(-) create mode 100644 tools/patches/agbcc-sdk-loop-cc-reuse.patch create mode 100644 tools/scripts/test_sdk_loop_cc_reuse.py create mode 100644 tools/tests/fixtures/sdk_loop_cc_reuse.c diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 16208f20..40cd9642 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -24,6 +24,7 @@ jobs: /usr/bin/python3 tools/scripts/test_sdk_tst_matches.py make tools/agbcc_sdk_cc/installed /usr/bin/python3 tools/scripts/test_sdk_cc_reuse.py + /usr/bin/python3 tools/scripts/test_sdk_loop_cc_reuse.py /usr/bin/python3 tools/scripts/test_sdk_lsr_flags.py --compiler tools/agbcc_sdk_flags/bin/agbcc --reference-compiler tools/agbcc_tst/bin/agbcc /usr/bin/python3 tools/scripts/test_sdk_halfword_alignment.py --compiler tools/agbcc_sdk_flags/bin/agbcc --reference-compiler tools/agbcc_tst/bin/agbcc /usr/bin/python3 tools/scripts/test_sdk_trampoline_alignment.py --compiler tools/agbcc_sdk_flags/bin/agbcc --reference-compiler tools/agbcc_tst/bin/agbcc diff --git a/Makefile b/Makefile index 75c15a86..5a937e6a 100644 --- a/Makefile +++ b/Makefile @@ -108,7 +108,7 @@ tools/agbcc_tst/installed: tools/install_agbcc_tst.sh tools/patches/agbcc-tst-eq $(BUILD)/src/m4a_1.o: Makefile tools/agbcc_tst/installed -tools/agbcc_sdk_cc/installed: tools/install_agbcc_sdk_cc.sh tools/patches/agbcc-tst-eq-ne-only.patch tools/patches/agbcc-sdk-cc-reuse.patch tools/patches/agbcc-sdk-label-cc-reuse.patch tools/patches/agbcc-sdk-sign-cc-reuse.patch +tools/agbcc_sdk_cc/installed: tools/install_agbcc_sdk_cc.sh tools/patches/agbcc-tst-eq-ne-only.patch tools/patches/agbcc-sdk-cc-reuse.patch tools/patches/agbcc-sdk-label-cc-reuse.patch tools/patches/agbcc-sdk-sign-cc-reuse.patch tools/patches/agbcc-sdk-loop-cc-reuse.patch sh tools/install_agbcc_sdk_cc.sh $(BUILD)/src/m4a_1_engine.o: Makefile tools/agbcc_sdk_cc/installed diff --git a/tools/install_agbcc_sdk_cc.sh b/tools/install_agbcc_sdk_cc.sh index 6230f23e..b1e7c9ad 100644 --- a/tools/install_agbcc_sdk_cc.sh +++ b/tools/install_agbcc_sdk_cc.sh @@ -11,6 +11,8 @@ LABEL_PATCH="$ROOT/tools/patches/agbcc-sdk-label-cc-reuse.patch" LABEL_SHA=6492f5ca23c68a471f4cb4c92e32ed4344984ea73b1c4d12cd52f1ffbf805d5f SIGN_PATCH="$ROOT/tools/patches/agbcc-sdk-sign-cc-reuse.patch" SIGN_SHA=f434c8999569964798b40adfb9d29a5dde6bfbb04395d697feebe4b2820217e0 +LOOP_PATCH="$ROOT/tools/patches/agbcc-sdk-loop-cc-reuse.patch" +LOOP_SHA=4de75458ad2a2472c5b83e7254abd9cacae5ac53b951385d30b4b4f194fc5beb TST_PATCH="$ROOT/tools/patches/agbcc-tst-eq-ne-only.patch" TST_SHA=6e4c7ada5e1dd97a0c6a7c9c105e02ac68e1948bfdd945b892b111302b311ade mkdir -p "$DEST/bin" "$DEST/source" @@ -23,6 +25,7 @@ printf '%s %s\n' "$ARCHIVE_SHA" "$ARCHIVE" | sha256sum -c - printf '%s %s\n' "$PATCH_SHA" "$PATCH" | sha256sum -c - printf '%s %s\n' "$LABEL_SHA" "$LABEL_PATCH" | sha256sum -c - printf '%s %s\n' "$SIGN_SHA" "$SIGN_PATCH" | sha256sum -c - +printf '%s %s\n' "$LOOP_SHA" "$LOOP_PATCH" | sha256sum -c - printf '%s %s\n' "$TST_SHA" "$TST_PATCH" | sha256sum -c - python3 - "$ARCHIVE" "$DEST/source" <<'PY' import os, sys, zipfile @@ -35,7 +38,7 @@ with zipfile.ZipFile(sys.argv[1]) as archive: os.chmod(os.path.join(sys.argv[2], entry.filename), mode) PY SOURCE="$DEST/source/agbcc-$REV" -(cd "$SOURCE" && patch --fuzz=0 -p1 < "$TST_PATCH" && patch --fuzz=0 -p1 < "$PATCH" && patch --fuzz=0 -p1 < "$LABEL_PATCH" && patch --fuzz=0 -p1 < "$SIGN_PATCH") +(cd "$SOURCE" && patch --fuzz=0 -p1 < "$TST_PATCH" && patch --fuzz=0 -p1 < "$PATCH" && patch --fuzz=0 -p1 < "$LABEL_PATCH" && patch --fuzz=0 -p1 < "$SIGN_PATCH" && patch --fuzz=0 -p1 < "$LOOP_PATCH") make -C "$SOURCE/gcc" -j1 normal EXE= [ ! -f "$SOURCE/gcc/agbcc.exe" ] || EXE=.exe @@ -47,6 +50,7 @@ cp "$SOURCE/gcc/COPYING" "$DEST/COPYING" printf 'base_tst_patch_sha256=%s\n' "$TST_SHA" printf 'label_patch_sha256=%s\n' "$LABEL_SHA" printf 'sign_patch_sha256=%s\n' "$SIGN_SHA" + printf 'loop_patch_sha256=%s\n' "$LOOP_SHA" sha256sum "$DEST/bin/agbcc$EXE" } > "$DEST/installed" echo "SDK comparison-flags compiler ready: $DEST/bin/agbcc$EXE" diff --git a/tools/patches/README.sdk-cc-reuse.md b/tools/patches/README.sdk-cc-reuse.md index 55afb15f..42e4ceb6 100644 --- a/tools/patches/README.sdk-cc-reuse.md +++ b/tools/patches/README.sdk-cc-reuse.md @@ -123,3 +123,38 @@ and on the taken path. Assembly requires PL/MI for the positive fixtures, retained CMP for GT and opaque controls, and a fresh CMP for the subsequent ordered consumer. Option-off and sign-without-base output remain reference byte-identical. This compiler capability adds no matching-function credit. + +## Byte-seeded loop decrement extension + +The separate `agbcc-sdk-loop-cc-reuse.patch` has SHA256 +`4de75458ad2a2472c5b83e7254abd9cacae5ac53b951385d30b4b4f194fc5beb`. +Default-off `-fthumb-loop-cc-reuse` requires base comparison reuse; production +module flags do not enable it. + +The proof accepts only a low SI counter loaded from a nonvolatile unsigned +byte before a single-entry loop, with no other counter definitions, calls or +opaque ASM in the body. The sole decrement is immediately followed by a zero +comparison and a signed GT backedge. Every region label's outside incoming +edges are checked, and unknown indirect/switch jumps are rejected. The byte +seed scan is bounded to eight instructions, region traversal to 256 nodes +and the whole-function entry audit to 4096 nodes. Budget exhaustion rejects +the optimization. Full INSN register-set checks include writeback notes. + +Inductively, the first input lies in 0..255; repeated iterations receive a +positive previous result. Only SUB #1 changes it, so its result is -1..254 +and overflow is zero. Its NZV therefore matches CMP(result,0) for signed GT. +A shared bounded proof additionally rejects any later observer of the +different carry bit on either path. The fact is cleared after the consumer. +This reasoning does not apply to arbitrary 32-bit counters at INT_MIN. + +Run `python3 tools/scripts/test_sdk_loop_cc_reuse.py` (the same compiler, +reference, dependency and output arguments as the main CC suite). Six +profiles cover reference, off, base only, loop plus base, all reuse options +combined and loop without base. They test every byte seed with all incoming NZCV combinations and +ABI/memory/return checks. Negative controls cover arbitrary word counters +including INT_MIN, changed counters, outside entry to an inner label, body +ASM, volatile seeds, real C calls, opaque exit paths and the traversal bound. +Only the admitted byte loop loses its comparison; all excluded loops retain +it. A non-returning loop with the expected r0 already present must fail the +return check. Off and loop-without-base text are reference byte-identical. +CI runs this ROM-free suite. No SDK function is activated or credited here. diff --git a/tools/patches/agbcc-sdk-loop-cc-reuse.patch b/tools/patches/agbcc-sdk-loop-cc-reuse.patch new file mode 100644 index 00000000..35fba597 --- /dev/null +++ b/tools/patches/agbcc-sdk-loop-cc-reuse.patch @@ -0,0 +1,166 @@ +--- a/gcc/toplev.c ++++ b/gcc/toplev.c +@@ -596,6 +596,7 @@ + int flag_thumb_cc_reuse = 0; + int flag_thumb_label_cc_reuse = 0; + int flag_thumb_sign_cc_reuse = 0; ++int flag_thumb_loop_cc_reuse = 0; + + typedef struct + { +@@ -745,6 +746,8 @@ + {"prologue-bugfix", &flag_prologue_bugfix, 1, + "Prevent unnecessary saving of the lr register to the stack"}, + #endif ++ {"thumb-loop-cc-reuse", &flag_thumb_loop_cc_reuse, 1, ++ "Reuse signed decrement flags in proven byte-seeded loops"}, + {"thumb-sign-cc-reuse", &flag_thumb_sign_cc_reuse, 1, + "Reuse Thumb arithmetic sign flags for sign-bit tests"}, + {"thumb-label-cc-reuse", &flag_thumb_label_cc_reuse, 1, +--- a/gcc/thumb.h ++++ b/gcc/thumb.h +@@ -1115,3 +1115,5 @@ + + extern int flag_thumb_sign_cc_reuse; + #define CC_THUMB_SIGN_ONLY 0400 ++ ++extern int flag_thumb_loop_cc_reuse; +--- a/gcc/thumb.c ++++ b/gcc/thumb.c +@@ -1685,6 +1685,116 @@ + return 0; + } + ++ ++/* Known direct CFG edges only. Return epilogues have no local successor. */ ++static int ++thumb_loop_cc_return(rtx p) ++{ ++ return GET_CODE(p) == RETURN ++ || (GET_CODE(p) == UNSPEC_VOLATILE && XINT(p, 1) == 1 ++ && XVECLEN(p, 0) == 1 ++ && GET_CODE(XVECEXP(p, 0, 0)) == CONST_INT ++ && INTVAL(XVECEXP(p, 0, 0)) == 0); ++} ++ ++static rtx ++thumb_loop_cc_target(rtx jump) ++{ ++ rtx p = PATTERN(jump), source, yes, no; ++ if (GET_CODE(p) != SET || SET_DEST(p) != pc_rtx) return 0; ++ source = SET_SRC(p); ++ if (GET_CODE(source) == LABEL_REF) return XEXP(source, 0); ++ if (GET_CODE(source) != IF_THEN_ELSE) return 0; ++ yes = XEXP(source, 1); no = XEXP(source, 2); ++ if (GET_CODE(yes) == LABEL_REF && no == pc_rtx) return XEXP(yes, 0); ++ if (yes == pc_rtx && GET_CODE(no) == LABEL_REF) return XEXP(no, 0); ++ return 0; ++} ++ ++/* A unique entry seeds 0..255. Only SUB #1 changes the counter, and ++ the sole repeat edge requires its signed result >0. Inductively every ++ SUB input is 0..255, hence result -1..254 and V=0. This is not valid ++ for an arbitrary SI decrement at INT_MIN. */ ++static int ++thumb_loop_cc_range(rtx sub, rtx latch, rtx header, rtx value) ++{ ++ rtx scan, p, source, labels[256], target; ++ int count = 0, budget = 256, i, inside = 0, found_sub = 0, found_latch = 0; ++ if (GET_CODE(header) != CODE_LABEL || LABEL_NUSES(header) != 1 ++ || JUMP_LABEL(latch) != header) return 0; ++ for (scan = header; scan && budget-- > 0; scan = NEXT_INSN(scan)) { ++ if (GET_CODE(scan) == CODE_LABEL) labels[count++] = scan; ++ else if (GET_CODE(scan) == NOTE || GET_CODE(scan) == BARRIER) ; ++ else { ++ if (GET_RTX_CLASS(GET_CODE(scan)) != 'i' ++ || GET_CODE(scan) == CALL_INSN) return 0; ++ p = PATTERN(scan); ++ if (GET_CODE(p) == ASM_INPUT || asm_noperands(p) >= 0) return 0; ++ if (scan == sub) found_sub = 1; ++ else if (reg_set_p(value, scan)) return 0; ++ if (GET_CODE(scan) == JUMP_INSN ++ && !thumb_loop_cc_return(p) && !thumb_loop_cc_target(scan)) return 0; ++ } ++ if (scan == latch) { found_latch = 1; break; } ++ } ++ if (!found_sub || !found_latch) return 0; ++ /* The only outside entry is fallthrough through the seed. */ ++ budget = 4096; ++ for (scan = get_insns(); scan && budget-- > 0; scan = NEXT_INSN(scan)) { ++ if (scan == header) inside = 1; ++ if (GET_CODE(scan) == JUMP_INSN && !thumb_loop_cc_return(PATTERN(scan))) { ++ target = thumb_loop_cc_target(scan); ++ if (!target) return 0; ++ if (!inside) for (i = 0; i < count; ++i) ++ if (target == labels[i]) return 0; ++ } ++ if (scan == latch) inside = 0; ++ } ++ if (scan) return 0; ++ budget = 8; ++ for (scan = prev_nonnote_insn(header); scan && budget-- > 0; ++ scan = prev_nonnote_insn(scan)) { ++ if (GET_CODE(scan) != INSN) return 0; ++ p = PATTERN(scan); ++ if (GET_CODE(p) != SET || asm_noperands(p) >= 0) return 0; ++ if (reg_set_p(value, scan)) { ++ if (!rtx_equal_p(SET_DEST(p), value)) return 0; ++ source = SET_SRC(p); ++ return GET_CODE(source) == ZERO_EXTEND && GET_MODE(source) == SImode ++ && GET_CODE(XEXP(source, 0)) == MEM ++ && GET_MODE(XEXP(source, 0)) == QImode ++ && !side_effects_p(XEXP(source, 0)); ++ } ++ if (side_effects_p(SET_DEST(p)) || side_effects_p(SET_SRC(p))) return 0; ++ } ++ return 0; ++} ++ ++static int ++thumb_loop_cc_consumer(rtx sub, rtx value) ++{ ++ rtx compare = next_nonnote_insn(sub), latch, p, source, condition, yes, no, header; ++ int budget = 256; ++ if (!compare || GET_CODE(compare) != INSN) return 0; ++ p = PATTERN(compare); ++ if (GET_CODE(p) != SET || SET_DEST(p) != cc0_rtx ++ || !rtx_equal_p(SET_SRC(p), value)) return 0; ++ latch = next_nonnote_insn(compare); ++ if (!latch || GET_CODE(latch) != JUMP_INSN) return 0; ++ p = PATTERN(latch); ++ if (GET_CODE(p) != SET || SET_DEST(p) != pc_rtx) return 0; ++ source = SET_SRC(p); ++ if (GET_CODE(source) != IF_THEN_ELSE) return 0; ++ condition = XEXP(source, 0); yes = XEXP(source, 1); no = XEXP(source, 2); ++ if (GET_CODE(condition) != GT || XEXP(condition, 0) != cc0_rtx ++ || XEXP(condition, 1) != const0_rtx ++ || GET_CODE(yes) != LABEL_REF || no != pc_rtx) return 0; ++ header = XEXP(yes, 0); ++ return thumb_loop_cc_range(sub, latch, header, value) ++ && thumb_cc_reuse_flags_dead(header, &budget) ++ && thumb_cc_reuse_flags_dead(next_nonnote_insn(latch), &budget); ++} ++ + void + thumb_sdk_notice_update_cc(rtx exp, rtx insn) + { +@@ -1713,6 +1823,19 @@ + return; + } + if (thumb_cc_reuse_store(insn)) return; ++ if (flag_thumb_loop_cc_reuse && GET_CODE(dest) == REG ++ && GET_MODE(dest) == SImode && REGNO(dest) < 8 ++ && GET_CODE(source) == PLUS ++ && rtx_equal_p(XEXP(source, 0), dest) ++ && GET_CODE(XEXP(source, 1)) == CONST_INT ++ && INTVAL(XEXP(source, 1)) == -1 ++ && thumb_loop_cc_consumer(insn, dest)) { ++ CC_STATUS_INIT; ++ cc_status.value1 = dest; ++ thumb_cc_reuse_zero_only = 1; ++ return; ++ } ++ + if (flag_thumb_sign_cc_reuse && GET_CODE(dest) == REG + && GET_MODE(dest) == SImode && REGNO(dest) < 8 + && GET_CODE(source) == PLUS diff --git a/tools/scripts/test_sdk_loop_cc_reuse.py b/tools/scripts/test_sdk_loop_cc_reuse.py new file mode 100644 index 00000000..b757f6e2 --- /dev/null +++ b/tools/scripts/test_sdk_loop_cc_reuse.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys,struct,json,re,argparse,subprocess +ROOT=Path(__file__).resolve().parents[2] +parser=argparse.ArgumentParser(description='Execute generic bounded byte-loop CC fixtures; no ROM required.') +parser.add_argument('--compiler',type=Path,default=ROOT/'tools/agbcc_sdk_cc/bin/agbcc') +parser.add_argument('--reference-compiler',type=Path,default=ROOT/'tools/agbcc_tst/bin/agbcc') +parser.add_argument('--deps',type=Path) +parser.add_argument('--out',type=Path,default=ROOT/'build/sdk_loop_cc_tests') +args=parser.parse_args() +if args.deps:sys.path.insert(0,str(args.deps.resolve())) +def executable(p): + p=p.resolve() + return p.with_suffix('.exe') if not p.is_file() and p.with_suffix('.exe').is_file() else p +from unicorn import Uc,UC_ARCH_ARM,UC_MODE_THUMB +from unicorn.arm_const import * +from elftools.elf.elffile import ELFFile +out=args.out.resolve();out.mkdir(parents=True,exist_ok=True);mask=0xffffffff +vals=[0,1,2,3,0x7fffffff,0x80000000,0x80000001,0xffffffff,0xfffffffe] +def signed(n):return n if n<0x80000000 else n-0x100000000 +def expected(name,a,b,initial): + n=initial if name=='loop_word' else b if name=='loop_inner_entry' and a&1 else initial&255 + total=0 + for iteration in range(300): + if name=='loop_inner_entry': + if not(iteration==0 and a&1):total+=2 + if total>4:return total,n + elif name=='loop_changed': + n=(n+b)&mask;total+=1 + if total==3:return total,n + else: + total=(total+a)&mask + if name=='loop_word' and total==(3*a)&mask:return total,n + n=(n-1)&mask + if signed(n)<=0:return total,n + raise AssertionError('oracle did not terminate') +def cases(name): + if name in ['loop_byte','loop_opaque','loop_volatile','loop_call','loop_opaque_exit']: + return [(a,0,n) for n in range(256) for a in [0,1,mask]] + if name=='loop_bound':return [(a,0,n) for n in [0,1,2] for a in [0,1,mask]] + if name=='loop_word':return [(a,0,n) for n in vals for a in vals] + if name=='loop_changed':return [(0,b,n) for n in [0,1,255] for b in vals] + if name=='loop_inner_entry':return [(a,b,n) for n in [0,1,255] for a in [0,1] for b in vals] + raise AssertionError(name) +base=0x1000000;data=0x2000000;stack=0x3000000;stop=base+0xf000;reports=[] +for profile,opts in [('reference',[]),('off',[]),('base',['-fthumb-cc-reuse']),('on',['-fthumb-cc-reuse','-fthumb-loop-cc-reuse']),('combined',['-fthumb-cc-reuse','-fthumb-loop-cc-reuse','-fthumb-label-cc-reuse','-fthumb-sign-cc-reuse']),('without_base',['-fthumb-loop-cc-reuse'])]: + compiler=executable(args.reference_compiler if profile=='reference' else args.compiler) + stem=out/profile + for cmd in [['arm-none-eabi-cpp','-undef','-nostdinc',ROOT/'tools/tests/fixtures/sdk_loop_cc_reuse.c','-o',stem.with_suffix('.i')],[str(compiler),'-O2','-mthumb-interwork','-fprologue-bugfix','-ftst',*opts,stem.with_suffix('.i'),'-o',stem.with_suffix('.s')],['arm-none-eabi-as','-mcpu=arm7tdmi','-o',stem.with_suffix('.o'),stem.with_suffix('.s')],['arm-none-eabi-ld','-Ttext=0x1000000','-e','loop_byte','-o',stem.with_suffix('.elf'),stem.with_suffix('.o')]]: + subprocess.run(cmd,cwd=ROOT,check=True) + with (out/(profile+'.elf')).open('rb') as f: + elf=ELFFile(f);code=elf.get_section_by_name('.text').data();syms={s.name:s['st_value']&~1 for s in elf.get_section_by_name('.symtab').iter_symbols() if s.name.startswith('loop_') and s.name!='loop_identity'} + if profile=='reference':reference=code + if profile in ['off','without_base']:assert code==reference + assembly=(out/(profile+'.s')).read_text() + assert re.search(r'^\s+bl\s+loop_identity\b',assembly,re.M),'call fixture was inlined' + for name in syms: + if name=='loop_negative_spin':continue + body=assembly.split('\n'+name+':\n')[1].split('\n.Lfe')[0] + count=len(re.findall(r'^\s+cmp\s',body,re.M));want=(0 if profile in ['on','combined'] else 1) if name=='loop_byte' else 2 if name in ['loop_word','loop_changed','loop_inner_entry'] else 1 + assert count==want,(profile,name,count,want) + mu=Uc(UC_ARCH_ARM,UC_MODE_THUMB);mu.mem_map(base,0x10000);mu.mem_write(base,code);mu.mem_map(data,0x1000);mu.mem_map(stack,0x1000) + preserved=[UC_ARM_REG_R4,UC_ARM_REG_R5,UC_ARM_REG_R6,UC_ARM_REG_R7,UC_ARM_REG_R8,UC_ARM_REG_R9,UC_ARM_REG_R10,UC_ARM_REG_R11] + count=0 + for name,address in syms.items(): + if name=='loop_negative_spin':continue + for a,b,initial in cases(name): + want,wantn=expected(name,a,b,initial) + for flags in range(16): + mu.reg_write(UC_ARM_REG_CPSR,(flags<<28)|0x30) + for reg,value in [(UC_ARM_REG_R0,a),(UC_ARM_REG_R1,b),(UC_ARM_REG_R2,data),(UC_ARM_REG_R3,a^b),(UC_ARM_REG_R12,0x12345678),(UC_ARM_REG_SP,stack+4080),(UC_ARM_REG_LR,stop|1)]:mu.reg_write(reg,value) + saved={reg:(0x44440000+i*4369^a^b)&mask for i,reg in enumerate(preserved)} + for reg,value in saved.items():mu.reg_write(reg,value) + mu.mem_write(data,struct.pack('0)goto again;out[1]=n;return sum;} +u32 loop_word(u32 a,u32 b,u32*out){u32 n=*out;u32 sum=0;again:sum+=a;if(sum==3*a)goto done;if((s32)--n>0)goto again;done:out[1]=n;return sum;} +u32 loop_changed(u32 a,u32 b,u32*out){u32 n=*(u8*)out;u32 sum=0;again:n+=b;sum++;if(sum==3)goto done;if((s32)--n>0)goto again;done:out[1]=n;return sum;} +u32 loop_inner_entry(u32 a,u32 b,u32*out){u32 n=b;u32 sum=0;if(a&1)goto middle;n=*(u8*)out;again:sum+=2;middle:if(sum>4)goto done;if((s32)--n>0)goto again;done:out[1]=n;return sum;} +u32 loop_opaque(u32 a,u32 b,u32*out){u32 n=*(u8*)out;u32 sum=0;again:sum+=a;__asm__ volatile("" ::: "cc");if((s32)--n>0)goto again;out[1]=n;return sum;} +u32 loop_volatile(u32 a,u32 b,u32*out){u32 n=*(volatile u8*)out;u32 sum=0;again:sum+=a;if((s32)--n>0)goto again;out[1]=n;return sum;} + +u32 loop_identity(u32 value){return value;} +u32 loop_call(u32 a,u32 b,u32*out){u32 n=*(u8*)out;u32 sum=0;again:sum=loop_identity(sum+a);if((s32)--n>0)goto again;out[1]=n;return sum;} +u32 loop_opaque_exit(u32 a,u32 b,u32*out){u32 n=*(u8*)out;u32 sum=0;again:sum+=a;if((s32)--n>0)goto again;out[1]=n;__asm__ volatile("" ::: "cc");return sum;} +#define STORES10 *target=sum; *target=sum; *target=sum; *target=sum; *target=sum; *target=sum; *target=sum; *target=sum; *target=sum; *target=sum; +u32 loop_bound(u32 a,u32 b,u32*out){u32 n=*(u8*)out;u32 sum=0;volatile u32*target=out+1;again:sum+=a; +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +STORES10 +if((s32)--n>0)goto again;out[1]=n;return sum;} +u32 loop_negative_spin(u32 a,u32 b,u32*out){for(;;){}}