Skip to content

Sign- and zero-extend 8- and 16-bit arguments to declared functions - #369

Merged
ASDAlexander77 merged 1 commit into
mainfrom
small-int-arg-extension
Sep 26, 2026
Merged

ASDAlexander77 merged 1 commit into
mainfrom
small-int-arg-extension

Conversation

@ASDAlexander77

@ASDAlexander77 ASDAlexander77 commented Sep 26, 2026 •

Copy link
Copy Markdown
Owner

On x86-64 Linux, a call from tslang into C that passes an 8- or 16-bit integer or a bool could hand the C function a wrong value. tslang never emitted signext/zeroext; nothing under lib/TypeScript set either.

Found by #367's end-to-end bindgen test on Linux CI (run 36246544195):

expected: narrow -5 65535 -5 false true
got:      narrow 251 65535 -5 false true

fx_widen_s8(int8_t v) { return v; }, built by clang at -O2, returned 251 for -5.

Cause

On x86-64 SysV, clang-compiled code expects whoever hands over a small integer to have extended it to 32 bits:

  • the caller, for an argument;
  • the callee, for a result.

LLVM does that extension only when the parameter or result is marked signext/zeroext. tslang passed the bare i8, so the callee read -5's 8 bits as 251 in a 32-bit register. u16 65535 came out right only because its zero extension is the same number.

On Windows x64 the callee extends, so the bug never showed there.

Fix

FuncOpLowering (LowerToLLVM.cpp) now marks the parameters and result of a declaration, a function with no body:

  • s8/s16: signext;
  • u8/u16/boolean: zeroext.

A direct call takes the callee declaration's attributes, so the caller now extends: movl $-5, %edi for take_s8(-5), checked with --emit=asm --mtriple=x86_64-unknown-linux-gnu.

Not changed

  • Definitions get no attributes. A signext parameter makes the callee trust its caller to have extended. tslang's indirect calls (closures, vtables, hybrid functions) carry no call-site attributes, so marking definitions would create new miscompiles in pure tslang code. So a tslang callback that C calls with a small integer, or whose small result C reads, is still unmarked.
  • Indirect calls through a C function pointer carry no call-site attributes.
  • Signless i8/i16 are left alone, since no type says which extension they need. tsbindgen emits sN/uN.

Tests

  • test-compile-small-int-ext-*: eight IR checks on small-int-ext/declare.ts, covering s8, s16, u8, u16, bool, an s8 and a u16 result, and s32 left unmarked.
    • The seven small-type checks failed before the change; the s32 control passed both before and after.
  • Full Release suite on Windows: 2892/2892 pass.
  • CI on this PR: Windows and Linux green.
  • End to end: tsbindgen v1: C headers to tslang bindings #367's branch with this commit cherry-picked, on Linux CI (run 36250596356): 2907/2907 pass. test-bindgen-plain and test-bindgen-namespace now pass, including the narrow row that failed without this fix.

🤖 Generated with Claude Code

clang-compiled callees on x86-64 SysV trust the caller to have extended them;
tslang passed bare i8/i16, so int8_t -5 arrived as 251.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ASDAlexander77
ASDAlexander77 merged commit 2f68fb6 into main Sep 26, 2026
2 checks passed
@ASDAlexander77
ASDAlexander77 deleted the small-int-arg-extension branch September 26, 2026 17:24
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.

1 participant