Skip to content

tccrun: reject TLS by SHF_TLS scan; add LLP64 regression cases to 73_arm64 - #3

Merged
dylan-conway merged 1 commit into
oven-sh:mobfrom
robobun:winarm64-shf-tls
Jul 20, 2026
Merged

tccrun: reject TLS by SHF_TLS scan; add LLP64 regression cases to 73_arm64#3
dylan-conway merged 1 commit into
oven-sh:mobfrom
robobun:winarm64-shf-tls

Conversation

@robobun

@robobun robobun commented Jul 14, 2026

Copy link
Copy Markdown
Collaborator

tcc_relocate_ex's TLS guard checked only the two canonical .tdata/.tbss
section pointers. A linked object built with -fdata-sections emits
.tdata./.tbss., which tcc_load_object_file keeps as separate
SHF_TLS sections, so the guard missed them and the Local-Exec accesses
silently aliased the host's TLS block. Scan every section for SHF_TLS
instead (the same idiom tccelf.c and the *-link.c relocators already use).

The scan stays before tcc_add_runtime so it covers only user-added
sections: user inputs are added via tcc_add_file() before tcc_relocate()
runs, whereas tcc_add_runtime pulls in tcc's own support objects (bcheck.o,
libtcc1.a). A gcc-built bcheck.o (the documented libtcc1-usegcc=yes perf
knob) carries its own __thread no_checking in .tbss, and sweeping that
would make every -b -run fail on a TLS-free program.

Also add two 64-bit add-immediate cases to tests2/73_arm64.c so an
LLP64-built tcc (Windows) has a regression guard for the 8a6cbc1
immediate-mask fix: before that fix, x + (1ll<<49) and x + 0x100000000
encode as add #0 and print 3e8 instead of 20000000003e8 / 1000003e8.


Found during adversarial review of the Windows ARM64 enablement for Bun
(oven-sh/bun#33696). Verified:

  • gcc -c -fdata-sections on a __thread int x=42 yields
    .tdata.tls_var1/.tbss.tls_var2; before this patch
    ./tcc obj.o -run main.c prints f()=0 with exit 0; after, it correctly
    errors with "thread-local storage is not supported at run time".
  • ./tcc -b -run hello.c with a gcc-built bcheck.o (usegcc=yes,
    .tbss present per readelf) still prints hello / exit 0.
  • tests2/73_arm64 passes on LP64 (Linux x64).
  • tests2/144_tls still rejected.

robobun added a commit to oven-sh/bun that referenced this pull request Jul 14, 2026
Apply the SHF_TLS section scan from oven-sh/tinycc#3 as a build patch so
the guard also catches per-symbol .tdata.foo/.tbss.foo sections from
linked object files (gcc -fdata-sections). The previous two-pointer
check only saw the canonical .tdata/.tbss sections that tcc's own
codegen writes into.

Extend the cc.test.ts TLS matrix to include uninitialized thread-locals,
which land in .tbss rather than .tdata, so both arms of the guard are
exercised.
@robobun
robobun changed the base branch from dev to mob July 14, 2026 23:10
…arm64

tcc_relocate_ex's TLS guard checked only the two canonical .tdata/.tbss
section pointers. A linked object built with -fdata-sections emits
.tdata.<sym>/.tbss.<sym>, which tcc_load_object_file keeps as separate
SHF_TLS sections, so the guard missed them and the Local-Exec accesses
silently aliased the host's TLS block. Scan every section for SHF_TLS
instead (the same idiom tccelf.c and the *-link.c relocators use), and
run the scan after pe_output_file/tcc_add_runtime so late-loaded objects
are covered.

Also add two 64-bit add-immediate cases to tests2/73_arm64.c so an
LLP64-built tcc (Windows) has a regression guard for the 8a6cbc1
immediate-mask fix: before that fix, x + (1ll<<49) and x + 0x100000000
encode as add #0 and print 3e8 instead of 20000000003e8 / 1000003e8.
@robobun
robobun force-pushed the winarm64-shf-tls branch from 818c736 to e2643dc Compare July 15, 2026 00:13
robobun added a commit to oven-sh/bun that referenced this pull request Jul 15, 2026
Placing the scan after tcc_add_runtime also swept tcc's own support
objects, which under the documented libtcc1-usegcc=yes knob makes every
"tcc -b -run" fail on a TLS-free program (gcc-built bcheck.o carries a
__thread no_checking in .tbss). User inputs are added via tcc_add_file
before tcc_relocate runs, so the original position already covers them.
Matches oven-sh/tinycc#3 at e2643dcc.
@dylan-conway
dylan-conway merged commit 05f0faf into oven-sh:mob Jul 20, 2026
robobun added a commit to oven-sh/bun that referenced this pull request Jul 20, 2026
Apply the SHF_TLS section scan from oven-sh/tinycc#3 as a build patch so
the guard also catches per-symbol .tdata.foo/.tbss.foo sections from
linked object files (gcc -fdata-sections). The previous two-pointer
check only saw the canonical .tdata/.tbss sections that tcc's own
codegen writes into.

Extend the cc.test.ts TLS matrix to include uninitialized thread-locals,
which land in .tbss rather than .tdata, so both arms of the guard are
exercised.
robobun added a commit to oven-sh/bun that referenced this pull request Jul 20, 2026
Placing the scan after tcc_add_runtime also swept tcc's own support
objects, which under the documented libtcc1-usegcc=yes knob makes every
"tcc -b -run" fail on a TLS-free program (gcc-built bcheck.o carries a
__thread no_checking in .tbss). User inputs are added via tcc_add_file
before tcc_relocate runs, so the original position already covers them.
Matches oven-sh/tinycc#3 at e2643dcc.
robobun added a commit to oven-sh/bun that referenced this pull request Jul 20, 2026
The SHF_TLS section-scan guard is now in the fork's mob branch, so drop
the build-time patch that carried it.
dylan-conway pushed a commit to oven-sh/bun that referenced this pull request Jul 20, 2026
## Summary

Supersedes #33653 (the TinyCC upgrade; the first commit here is that
PR's content) and #29476. Enables `bun:ffi` on Windows ARM64: drops the
windows-arm64 exclusion from `cfg.tinycc`, the generated `ENABLE_TINYCC`
constant, and the `tcc_sys` link stubs, un-skips the FFI tests that were
gated on the platform, and bumps the TinyCC pin to
[`oven-sh/tinycc@05f0fafa`](oven-sh/tinycc@05f0faf)
(which is
[`8a6cbc12`](oven-sh/tinycc@8a6cbc1)
plus oven-sh/tinycc#3).

### The windows-arm64 bug this uncovered (root-caused and fixed in the
TinyCC fork)

Enabling the platform made the windows-11-aarch64 CI lane run the FFI
suites on real hardware for the first time, which exposed wrong doubles
(`sum(0.5…9.5)` = 46 instead of 50) and segfaults in every JSCallback
test. Root cause, proven with an in-CI probe that dumped the JIT machine
code from the runner:

TinyCC's arm64 backend decides whether a 64-bit constant fits an ADD/SUB
immediate with `!(val & ~0xffful)`. On an **LLP64 host** (which is
exactly how Bun builds TinyCC into `bun.exe` on Windows via clang-cl)
`unsigned long` is 32 bits, so `~0xffful` zero-extends to
`0x00000000fffff000`, and any constant with no bits in [12,32) "fits".
`1ll << 49` is JSC's `DoubleEncodeOffset`, used by every `bun:ffi`
trampoline, and it compiled to `add xN, xN, #0`:

- machine code on the CI runner: `… ldur x0,[x29,#-8]; add x0,x0,#0 …`
- same source, LP64-built tcc: `… mov x30,#0x2000000000000; add
x0,x0,x30 …`

So every double crossing the FFI boundary lost the NaN-boxing offset (JS
saw `bits − 2^49`: 12.25→11.25, 50→46) and pointer arguments decoded to
wild addresses (the segfaults). LP64-built TinyCC (Linux/macOS) is
unaffected; the fix produces byte-identical output there, which is why
the bug only ever existed on Windows ARM64. Fixed in the fork
(`arm64_gen_opic`, `arm64_check_offset`, `arm64_sym`) by using
`uint64_t`-typed masks; TinyCC's own test suite passes, and
oven-sh/tinycc#3 adds regression cases to `tests2/73_arm64`. This also
affects any natively-built windows-arm64 TinyCC upstream.

### Known parity notes (pre-existing, unchanged)

- `cc()` code with a >4 KB stack frame needs `__chkstk`, which Bun
doesn't provide on any Windows target.
- `long double` soft-float helpers aren't provided on any arm64 target.

## Test plan

- [x] Linux/macOS/Windows-x64 behavior unchanged (same suites as #33653;
LP64 tcc output byte-identical across the fix)
- [x] Cross-built windows-arm64 `bun.exe` from Linux links with TinyCC
enabled
- [x] windows-11-aarch64 CI lane: `bun:ffi` suites green with the LLP64
fix (build #71302, and re-verified locally on Windows 11 ARM64 at
`e83d3d00`)

Fixes #28055

---------

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
liooil pushed a commit to liooil/poly that referenced this pull request Aug 7, 2026
## Summary

Supersedes #33653 (the TinyCC upgrade; the first commit here is that
PR's content) and #29476. Enables `bun:ffi` on Windows ARM64: drops the
windows-arm64 exclusion from `cfg.tinycc`, the generated `ENABLE_TINYCC`
constant, and the `tcc_sys` link stubs, un-skips the FFI tests that were
gated on the platform, and bumps the TinyCC pin to
[`oven-sh/tinycc@05f0fafa`](oven-sh/tinycc@05f0faf)
(which is
[`8a6cbc12`](oven-sh/tinycc@8a6cbc1)
plus oven-sh/tinycc#3).

### The windows-arm64 bug this uncovered (root-caused and fixed in the
TinyCC fork)

Enabling the platform made the windows-11-aarch64 CI lane run the FFI
suites on real hardware for the first time, which exposed wrong doubles
(`sum(0.5…9.5)` = 46 instead of 50) and segfaults in every JSCallback
test. Root cause, proven with an in-CI probe that dumped the JIT machine
code from the runner:

TinyCC's arm64 backend decides whether a 64-bit constant fits an ADD/SUB
immediate with `!(val & ~0xffful)`. On an **LLP64 host** (which is
exactly how Bun builds TinyCC into `bun.exe` on Windows via clang-cl)
`unsigned long` is 32 bits, so `~0xffful` zero-extends to
`0x00000000fffff000`, and any constant with no bits in [12,32) "fits".
`1ll << 49` is JSC's `DoubleEncodeOffset`, used by every `bun:ffi`
trampoline, and it compiled to `add xN, xN, #0`:

- machine code on the CI runner: `… ldur x0,[x29,#-8]; add x0,x0,#0 …`
- same source, LP64-built tcc: `… mov x30,#0x2000000000000; add
x0,x0,x30 …`

So every double crossing the FFI boundary lost the NaN-boxing offset (JS
saw `bits − 2^49`: 12.25→11.25, 50→46) and pointer arguments decoded to
wild addresses (the segfaults). LP64-built TinyCC (Linux/macOS) is
unaffected; the fix produces byte-identical output there, which is why
the bug only ever existed on Windows ARM64. Fixed in the fork
(`arm64_gen_opic`, `arm64_check_offset`, `arm64_sym`) by using
`uint64_t`-typed masks; TinyCC's own test suite passes, and
oven-sh/tinycc#3 adds regression cases to `tests2/73_arm64`. This also
affects any natively-built windows-arm64 TinyCC upstream.

### Known parity notes (pre-existing, unchanged)

- `cc()` code with a >4 KB stack frame needs `__chkstk`, which Bun
doesn't provide on any Windows target.
- `long double` soft-float helpers aren't provided on any arm64 target.

## Test plan

- [x] Linux/macOS/Windows-x64 behavior unchanged (same suites as #33653;
LP64 tcc output byte-identical across the fix)
- [x] Cross-built windows-arm64 `bun.exe` from Linux links with TinyCC
enabled
- [x] windows-11-aarch64 CI lane: `bun:ffi` suites green with the LLP64
fix (build #71302, and re-verified locally on Windows 11 ARM64 at
`e83d3d00`)

Fixes #28055

---------

Co-authored-by: robobun <117481402+robobun@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants