Compare commits

...

11 Commits

Author SHA1 Message Date
Petr Skocik
b39da9f6fa Fix false warnings with readonly atomics
Some checks failed
build and test / test-x86_64-linux (push) Has been cancelled
build and test / test-x86_64-osx (push) Has been cancelled
build and test / test-aarch64-osx (push) Has been cancelled
build and test / test-x86_64-win32 (push) Has been cancelled
build and test / test-i386-win32 (push) Has been cancelled
build and test / test-armv7-linux (push) Has been cancelled
build and test / test-aarch64-linux (push) Has been cancelled
build and test / test-riscv64-linux (push) Has been cancelled
Code like:

    #include <stdatomic.h>
    _Atomic const int ai; int aiGet(void){ return atomic_load(&ai); }

has been raising "warning: assignment of read-only location".

This is due to `__typeof__ (*ptr) tmp;` not rvalue-converting the target
type.
2026-04-28 08:40:53 +02:00
Petr Skocik
5675177b7f allow $ at nonstarting positions in asm symbols under -fdollars-in-identifiers
e.g., asm("foo$bar: ret;");
2026-04-28 08:36:09 +02:00
Reini Urban
543d84c668 .gitignore: Add tests/libtcc_test_xor_rex 2026-04-26 14:56:39 +02:00
Detlef Riekenberg
9b8765d8ba Revert commits from OpenCode-AI and a partial regression fix
This reverts the crap from an unknown committer "OpenCode[AI]", that added an extension to TinyCC,
which is incompatible to the C standard and not available in any other mainstream C compiler:

* Revert "No More ; needed"
This reverts commit 6547ea47d6

* Revert "Add AGENTS.md"
This reverts commit 898f496dc9.

This also reverts a regression fix by "Zuhaitz.dev", which fixed a small part of the OpenCode-AI chaos:
* Revert "tccgen: Fix optional semicolon regression"
This reverts commit 169628a6ab

--
Regards ... Detlef
2026-04-20 01:42:41 +02:00
Anon
7b92d2dcd2 Fix warning 2026-04-16 13:16:17 -07:00
Zuhaitz-dev
169628a6ab tccgen: Fix optional semicolon regression 2026-04-16 17:24:21 +01:00
Cyan Ogilvie
8ecfd0a722 Relicensing TinyCC 2026-04-16 10:45:24 -03:00
Cyan Ogilvie
690fb14015 x86_64-gen: fix missing REX prefix for xor zero into r8-r15
load() used a raw o() call to emit xor-zero which lost bit 3 of the
register number via REG_VALUE():

    o(0xc031 + REG_VALUE(r) * 0x900);

For r >= 8, this emitted the wrong instruction (e.g. xor %ebx,%ebx
for TREG_R11 instead of xor %r11d,%r11d), clobbering the wrong
register.

Use orex() to emit the REX prefix, consistent with all adjacent
branches in load().
2026-04-16 10:42:16 -03:00
Cyan Ogilvie
d5ecb52a71 tests: add test for x86_64 xor REX prefix bug in load()
When load() in x86_64-gen.c generates a zero constant for a 64-bit
register, it uses:

    o(0xc031 + REG_VALUE(r) * 0x900);

REG_VALUE(r) masks to (r & 7), losing bit 3, and no orex() call
emits the REX prefix needed for registers r8-r15.  For TREG_R11
(used by gcall_or_jmp for indirect calls), this emits
"xor %ebx,%ebx" (31 db) instead of "xor %r11d,%r11d" (45 31 db),
clobbering the wrong register.

The test compiles an indirect call through a null function pointer
((void(*)(void))0)() via libtcc, then inspects the generated machine
code for the incorrect encoding.
2026-04-16 10:42:16 -03:00
OpenCode[AI]
6547ea47d6 No more ; needed 2026-04-15 20:56:01 -07:00
OpenCode[AI]
898f496dc9 Add AGENTS.md 2026-04-15 20:28:42 -07:00
8 changed files with 145 additions and 6 deletions

1
.gitignore vendored
View File

@ -62,6 +62,7 @@ tests/*-cc*
tests/*-tcc*
tests/libtcc_test
tests/libtcc_test_mt
tests/libtcc_test_xor_rex
tests/asm-c-connect
tests/asm-c-connect-sep
tests/vla_test

View File

@ -53,6 +53,7 @@
Christian Jullien YES Windows Cygwin build and tests
Reimar Döffinger YES
noneofyourbusiness YES
Cyan Ogilvie YES
------------------------------------------------------------------------------

View File

@ -100,7 +100,7 @@ typedef struct {
__val; })
#define atomic_load_explicit(object, order) \
({ __typeof__ (object) ptr = (object); \
__typeof__ (*ptr) tmp; \
__typeof__ ((void)0,*ptr) tmp; \
__atomic_load (ptr, &tmp, (order)); \
tmp; \
})

View File

@ -1100,7 +1100,7 @@ static void tcc_assemble_inline(TCCState *s1, const char *str, int len, int glob
{
const int *saved_macro_ptr = macro_ptr;
int dotid = set_idnum('.', IS_ID);
#ifndef TCC_TARGET_RISCV64
#if !defined(TCC_TARGET_RISCV64) && !defined(TCC_TARGET_X86_64)
int dolid = set_idnum('$', 0);
#endif
@ -1110,7 +1110,7 @@ static void tcc_assemble_inline(TCCState *s1, const char *str, int len, int glob
tcc_assemble_internal(s1, 0, global);
tcc_close();
#ifndef TCC_TARGET_RISCV64
#if !defined(TCC_TARGET_RISCV64) && !defined(TCC_TARGET_X86_64)
set_idnum('$', dolid);
#endif
set_idnum('.', dotid);

View File

@ -2500,12 +2500,13 @@ static void parse_number(const char *p)
if (t >= b)
tcc_error("invalid digit");
n = n * b + t;
if (!ov)
if (!ov) {
/* detect overflow */
if (n1 >= 0x1000000000000000ULL && n / b != n1)
ov = 1;
else
n1 = n;
}
}
/* Determine the characteristics (unsigned and/or 64bit) the type of
@ -3711,7 +3712,7 @@ ST_FUNC void preprocess_start(TCCState *s1, int filetype)
s1->pack_stack[0] = 0;
s1->pack_stack_ptr = s1->pack_stack;
set_idnum('$', !is_asm && s1->dollars_in_identifiers ? IS_ID : 0);
set_idnum('$', s1->dollars_in_identifiers ? IS_ID : 0);
set_idnum('.', is_asm ? IS_ID : 0);
if (!(filetype & AFF_TYPE_ASM)) {

View File

@ -13,6 +13,7 @@ TESTS = \
hello-run \
libtest \
libtest_mt \
libtest_xor_rex \
test3 \
abitest \
asm-c-connect-test \
@ -40,6 +41,9 @@ endif
ifeq (,$(filter i386 x86_64,$(ARCH)))
TESTS := $(filter-out asm-c-connect-test,$(TESTS))
endif
ifeq (,$(filter x86_64,$(ARCH)))
TESTS := $(filter-out libtest_xor_rex,$(TESTS))
endif
ifeq ($(OS),Windows_NT) # for libtcc_test to find libtcc.dll
PATH := $(CURDIR)/$(TOP)$(if $(findstring ;,$(PATH)),;,:)$(PATH)
endif
@ -103,6 +107,9 @@ libtcc_test$(EXESUF): libtcc_test.c
libtcc_test_mt$(EXESUF): libtcc_test_mt.c
$(CC) -o $@ $< $(CFLAGS) $(-LTCC) $(LIBS)
libtcc_test_xor_rex$(EXESUF): libtcc_test_xor_rex.c
$(CC) -o $@ $< $(CFLAGS) $(-LTCC) $(LIBS)
%-dir:
@echo ------------ $@ ------------
$(MAKE) -k -C $*

128
tests/libtcc_test_xor_rex.c Normal file
View File

@ -0,0 +1,128 @@
/*
* Test for x86_64 xor REX prefix bug in load() -- x86_64-gen.c
*
* Bug: when loading a 64-bit zero constant into registers r8-r15,
* load() emits:
*
* o(0xc031 + REG_VALUE(r) * 0x900); // xor r, r
*
* REG_VALUE(r) masks to (r & 7), losing bit 3, and no orex() call
* emits the REX prefix needed for extended registers.
*
* Result: r=TREG_R11(11) -> REG_VALUE=3 -> emits 31 db (xor ebx,ebx)
* Correct: should emit 45 31 db (REX.RB xor r11d,r11d)
*
* Fix:
* orex(0, r, r, 0x31);
* o(0xc0 + REG_VALUE(r) * 9);
*
* Trigger: an indirect call through a compile-time null function pointer,
* e.g. ((void(*)(void))0)(), causes gcall_or_jmp() to fall into the
* "indirect call" path which does load(TREG_R11, <constant 0>).
*
* This test compiles such code via the libtcc API, then inspects the
* generated machine code for the incorrect encoding.
*/
#if !defined __x86_64__
#include <stdio.h>
int main(void) { printf("SKIP (x86_64 only)\n"); return 0; }
#else
#include <stdio.h>
#include "libtcc.h"
static void handle_error(void *opaque, const char *msg)
{
fprintf(opaque, "%s\n", msg);
}
/*
* Compiled via libtcc. The cast-to-null indirect call forces
* gcall_or_jmp() into its "else" branch (no VT_SYM, so the
* condition on line ~650 fails), which does:
*
* r = TREG_R11;
* load(r, vtop); // <-- buggy xor lands here
* o(0x41); o(0xff); // call/jmp *r
* o(0xd0 + REG_VALUE(r)); // r11 -> 0xd3
*
* We never execute the function (it would crash); we only
* inspect the generated bytes.
*/
static const char test_code[] =
"void test(void) {\n"
" ((void(*)(void))0)();\n"
"}\n";
int main(int argc, char **argv)
{
TCCState *s;
unsigned char *code;
int i;
int ret = 0;
s = tcc_new();
if (!s) {
fprintf(stderr, "tcc_new() failed\n");
return 2;
}
tcc_set_error_func(s, stderr, handle_error);
for (i = 1; i < argc; ++i) {
char *a = argv[i];
if (a[0] == '-') {
if (a[1] == 'B')
tcc_set_lib_path(s, a + 2);
else if (a[1] == 'I')
tcc_add_include_path(s, a + 2);
else if (a[1] == 'L')
tcc_add_library_path(s, a + 2);
}
}
tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
if (tcc_compile_string(s, test_code) == -1)
return 2;
if (tcc_relocate(s) < 0)
return 2;
code = (unsigned char *)tcc_get_symbol(s, "test");
if (!code) {
fprintf(stderr, "symbol 'test' not found\n");
return 2;
}
/*
* Scan for the 'call *%r11' instruction: 41 ff d3
* Then inspect the bytes immediately before it.
*
* Correct: 45 31 db 41 ff d3 (xor %r11d,%r11d ; call *%r11)
* Buggy: 31 db 41 ff d3 (xor %ebx,%ebx ; call *%r11)
*/
for (i = 3; i < 128; i++) {
if (code[i] == 0x41 && code[i+1] == 0xff && code[i+2] == 0xd3) {
if (i >= 3 && code[i-3] == 0x45
&& code[i-2] == 0x31
&& code[i-1] == 0xdb) {
printf("xor_rex: OK\n");
} else if (i >= 2 && code[i-2] == 0x31 && code[i-1] == 0xdb) {
printf("xor_rex: FAIL - xor %%ebx,%%ebx (31 db) emitted"
" instead of xor %%r11d,%%r11d (45 31 db)\n");
ret = 1;
} else {
printf("xor_rex: FAIL - unexpected bytes before"
" call *%%r11: %02x %02x %02x %02x\n",
code[i-4], code[i-3], code[i-2], code[i-1]);
ret = 1;
}
goto done;
}
}
printf("xor_rex: SKIP - call *%%r11 not found in generated code\n");
done:
tcc_delete(s);
return ret;
}
#endif

View File

@ -495,7 +495,8 @@ void load(int r, SValue *sv)
orex(0,r,0, 0xb8 + REG_VALUE(r)); /* mov $xx, r */
gen_le32(sv->c.i);
} else {
o(0xc031 + REG_VALUE(r) * 0x900); /* xor r, r */
orex(0, r, r, 0x31); /* xor r, r */
o(0xc0 + REG_VALUE(r) * 9);
}
} else {
orex(0,r,0, 0xb8 + REG_VALUE(r)); /* mov $xx, r */