From 179149b8bb3ddf18093fe08e48ee2423d3408d63 Mon Sep 17 00:00:00 2001 From: MatyMeatBoy <108221324+MatyMeatBoy@users.noreply.github.com> Date: Wed, 4 Nov 2026 20:08:41 -0300 Subject: [PATCH 1/2] Add opt-in later literal-pool placement within a reach budget Prefer a later existing barrier only when estimated code and conservative literal reservations remain within Thumb reach. Reuse legacy emission and fallback, keep production flags unchanged, and exercise actual linked constant loads and ABI state. 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 | 33 +++++ tools/patches/agbcc-sdk-late-pool.patch | 71 +++++++++++ tools/scripts/test_sdk_late_pool.py | 81 ++++++++++++ tools/tests/fixtures/sdk_late_pool.c | 159 ++++++++++++++++++++++++ 7 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 tools/patches/agbcc-sdk-late-pool.patch create mode 100644 tools/scripts/test_sdk_late_pool.py create mode 100644 tools/tests/fixtures/sdk_late_pool.c diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 40cd9642..c454890f 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -25,6 +25,7 @@ jobs: 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_late_pool.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 5a937e6a..e8a03a75 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/patches/agbcc-sdk-loop-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 tools/patches/agbcc-sdk-late-pool.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 b1e7c9ad..3c31b4fb 100644 --- a/tools/install_agbcc_sdk_cc.sh +++ b/tools/install_agbcc_sdk_cc.sh @@ -13,6 +13,8 @@ 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 +LATE_PATCH="$ROOT/tools/patches/agbcc-sdk-late-pool.patch" +LATE_SHA=5d03b24a211750423a708b5fdc207afe26e73216b49babeba2a769b8db9be408 TST_PATCH="$ROOT/tools/patches/agbcc-tst-eq-ne-only.patch" TST_SHA=6e4c7ada5e1dd97a0c6a7c9c105e02ac68e1948bfdd945b892b111302b311ade mkdir -p "$DEST/bin" "$DEST/source" @@ -26,6 +28,7 @@ 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' "$LATE_SHA" "$LATE_PATCH" | sha256sum -c - printf '%s %s\n' "$TST_SHA" "$TST_PATCH" | sha256sum -c - python3 - "$ARCHIVE" "$DEST/source" <<'PY' import os, sys, zipfile @@ -38,7 +41,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" && patch --fuzz=0 -p1 < "$LOOP_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" && patch --fuzz=0 -p1 < "$LATE_PATCH") make -C "$SOURCE/gcc" -j1 normal EXE= [ ! -f "$SOURCE/gcc/agbcc.exe" ] || EXE=.exe @@ -51,6 +54,7 @@ cp "$SOURCE/gcc/COPYING" "$DEST/COPYING" printf 'label_patch_sha256=%s\n' "$LABEL_SHA" printf 'sign_patch_sha256=%s\n' "$SIGN_SHA" printf 'loop_patch_sha256=%s\n' "$LOOP_SHA" + printf 'late_pool_patch_sha256=%s\n' "$LATE_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 42e4ceb6..9d42ae4c 100644 --- a/tools/patches/README.sdk-cc-reuse.md +++ b/tools/patches/README.sdk-cc-reuse.md @@ -158,3 +158,36 @@ 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. + +## Later existing literal pools + +The separate `agbcc-sdk-late-pool.patch` has SHA256 +`5d03b24a211750423a708b5fdc207afe26e73216b49babeba2a769b8db9be408`. +Its independent `-fthumb-late-pool` option defaults off. Production module +flags do not enable it. Legacy pool placement returns the first barrier; +the option may choose a later existing barrier within a conservative bound. + +The bounded 4096-node scan counts instruction lengths and reserves space for +every pending literal, intentionally overcounting duplicates. Halfword +constants reserve a word, matching reorg's promotion, and doubleword constants +reserve eight bytes. Prospective literal-load lengths are also bounded below. +Only a barrier with code plus reserved literals at most 1000 bytes qualifies, +leaving margin for alignment and Thumb PC bias within its 1020-byte reach. +Unsupported sizes stop the search. Without an admitted later barrier, the +compiler falls back to unchanged legacy placement. Existing pool/relocation emission is reused. +There are no forced placements, ROM/function exceptions or opcode rewrites. + +Run `python3 tools/scripts/test_sdk_late_pool.py`, accepting the same compiler, +reference, dependency and output arguments as the CC suite. Five profiles +execute 140,000 cases: reference, off, late placement, all options combined, +and late placement without TST. The off text is byte-identical to reference; +the positive fixture must move its word pool after the return. + +Tests include 120 distinct constants and multiple barriers, a long function +with 520 volatile updates beyond the reach budget, 32/64-bit values and linked +read-only data references. They execute boundaries and seeded random values +with all incoming NZCV combinations and check memory, 32/64-bit results, +return PC, SP and r4-r11. Linked ELF sections are loaded at actual addresses. +A spinning negative control with the expected r0 already present must fail +return validation. CI runs these ROM-free tests. This capability does not +activate production flags or award a matching function. diff --git a/tools/patches/agbcc-sdk-late-pool.patch b/tools/patches/agbcc-sdk-late-pool.patch new file mode 100644 index 00000000..3f6b1665 --- /dev/null +++ b/tools/patches/agbcc-sdk-late-pool.patch @@ -0,0 +1,71 @@ +--- a/gcc/toplev.c ++++ b/gcc/toplev.c +@@ -597,6 +597,7 @@ + int flag_thumb_label_cc_reuse = 0; + int flag_thumb_sign_cc_reuse = 0; + int flag_thumb_loop_cc_reuse = 0; ++int flag_thumb_late_pool = 0; + + typedef struct + { +@@ -746,6 +747,8 @@ + {"prologue-bugfix", &flag_prologue_bugfix, 1, + "Prevent unnecessary saving of the lr register to the stack"}, + #endif ++ {"thumb-late-pool", &flag_thumb_late_pool, 1, ++ "Prefer later existing barriers within a conservative literal reach budget"}, + {"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, +--- a/gcc/thumb.c ++++ b/gcc/thumb.c +@@ -248,12 +248,49 @@ + + #define MAX_COUNT_SI 1000 + ++ ++extern int flag_thumb_late_pool; ++ ++/* Prefer a later existing dead-code boundary only while code plus a ++ conservative reservation for every literal remains in reach. Duplicate ++ constants are intentionally overcounted; HI loads become SI in reorg. */ ++static rtx ++thumb_find_late_pool_barrier(rtx scan) ++{ ++ rtx best = 0; ++ int code_bytes = 0, pool_bytes = 0, budget = 4096; ++ for (; scan && budget-- > 0; scan = NEXT_INSN(scan)) { ++ int length = get_attr_length(scan); ++ if (GET_CODE(scan) == INSN && GET_CODE(PATTERN(scan)) == SET) { ++ rtx p = PATTERN(scan), source = SET_SRC(p), dest = SET_DEST(p); ++ enum machine_mode mode = GET_MODE(dest); ++ if (dest != pc_rtx && fixit(source, mode)) { ++ int size = GET_MODE_SIZE(mode); ++ if (size < 4) size = 4; ++ if (size != 4 && size != 8) break; ++ pool_bytes += size; ++ if (length < size / 2) length = size / 2; ++ } ++ } ++ code_bytes += length; ++ /* Preserve margin for word alignment and Thumb's PC bias. */ ++ if (code_bytes + pool_bytes > MAX_COUNT_SI) break; ++ if (GET_CODE(scan) == BARRIER) best = scan; ++ } ++ return best; ++} ++ + static rtx + find_barrier(rtx from) + { + int count = 0; + rtx found_barrier = 0; + rtx label; ++ ++ if (flag_thumb_late_pool) { ++ rtx late = thumb_find_late_pool_barrier(from); ++ if (late) return late; ++ } + + while (from && count < MAX_COUNT_SI) + { diff --git a/tools/scripts/test_sdk_late_pool.py b/tools/scripts/test_sdk_late_pool.py new file mode 100644 index 00000000..d2ffd3f3 --- /dev/null +++ b/tools/scripts/test_sdk_late_pool.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +from pathlib import Path +import sys,struct,json,itertools,random,argparse,subprocess,re +ROOT=Path(__file__).resolve().parents[2] +parser=argparse.ArgumentParser(description='Execute generic Thumb literal-pool layout 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_late_pool_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 +values=[0,1,2,3,0x7fffffff,0x80000000,0x80000001,0xfffffffe,mask] +rng=random.Random(5361020);pairs=list(itertools.product(values,repeat=2))+[(rng.getrandbits(32),rng.getrandbits(32)) for _ in range(64)] +constants=[(0x13579bdf+i*0x01020409)&mask for i in range(120)];sentinel=0xa5a5a5a5 + +def expected(name,a,b): + if name=='pool_paths': + v=((0x13579bdf+b) if a&1 else (0x2468ace0-b))&mask + return ((v^0xa5c3e179) if a&2 else (v+0x10203040)&mask),v + if name=='pool_pointer': + v=([0x12345678,0x87654321,0x80000001,mask][a&3]+b)&mask + return v^0x55aa13c7,v + if name=='pool_wide':return (0x12345678fffffff8 if a&1 else 0xfedcba9887654321),sentinel + if name=='pool_many': + v=(constants[a]+b)&mask if abody.rindex('\tbx\t'),'positive fixture pool must follow its return' + with (out/(profile+'.elf')).open('rb') as f: + elf=ELFFile(f);code=elf.get_section_by_name('.text').data();sections=[(s['sh_addr'],s.data()) for s in elf.iter_sections() if s['sh_flags']&2 and s['sh_type']!='SHT_NOBITS'];syms={s.name:s['st_value']&~1 for s in elf.get_section_by_name('.symtab').iter_symbols() if s.name.startswith('pool_') and s['st_info']['type']=='STT_FUNC'} + if profile=='reference':reference=code + if profile=='off':assert code==reference,'default output changed' + if profile=='on':assert code!=reference,'pool fixture did not exercise a layout change' + mu=Uc(UC_ARCH_ARM,UC_MODE_THUMB);mu.mem_map(base,0x10000);mu.mem_map(data,0x1000);mu.mem_map(stack,0x1000) + for address,content in sections: + assert base<=address and address+len(content) Date: Wed, 4 Nov 2026 20:17:07 -0300 Subject: [PATCH 2/2] Skip empty allocated sections in the literal fixture loader GNU ld may keep an empty .data section outside the mapped code range. It has no bytes to load; ignore zero-sized sections while retaining address and content checks for every nonempty allocated section. Co-Authored-By: Codex --- tools/scripts/test_sdk_late_pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/scripts/test_sdk_late_pool.py b/tools/scripts/test_sdk_late_pool.py index d2ffd3f3..fe23da44 100644 --- a/tools/scripts/test_sdk_late_pool.py +++ b/tools/scripts/test_sdk_late_pool.py @@ -50,7 +50,7 @@ def cases(name): body=assembly.split('\npool_paths:\n',1)[1].split('\n.Lfe',1)[0] assert body.index('\t.word')>body.rindex('\tbx\t'),'positive fixture pool must follow its return' with (out/(profile+'.elf')).open('rb') as f: - elf=ELFFile(f);code=elf.get_section_by_name('.text').data();sections=[(s['sh_addr'],s.data()) for s in elf.iter_sections() if s['sh_flags']&2 and s['sh_type']!='SHT_NOBITS'];syms={s.name:s['st_value']&~1 for s in elf.get_section_by_name('.symtab').iter_symbols() if s.name.startswith('pool_') and s['st_info']['type']=='STT_FUNC'} + elf=ELFFile(f);code=elf.get_section_by_name('.text').data();sections=[(s['sh_addr'],s.data()) for s in elf.iter_sections() if s['sh_flags']&2 and s['sh_size']>0 and s['sh_type']!='SHT_NOBITS'];syms={s.name:s['st_value']&~1 for s in elf.get_section_by_name('.symtab').iter_symbols() if s.name.startswith('pool_') and s['st_info']['type']=='STT_FUNC'} if profile=='reference':reference=code if profile=='off':assert code==reference,'default output changed' if profile=='on':assert code!=reference,'pool fixture did not exercise a layout change'