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..fe23da44 --- /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_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' + 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)