Fix Windows ARM64 runtime regressions and coverage

This commit is contained in:
Benjamin Oldenburg 2026-03-15 20:26:18 +07:00
parent 7e7917c3c9
commit 396675f74f
18 changed files with 422 additions and 38 deletions

View File

@ -11,7 +11,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: make & test tcc (x86_64-linux)
run: ./configure && make && make test -k
run: |
./configure
make
make test -k
make -C tests/tests2 tests2.112 tests2.128
test-x86_64-osx:
runs-on: macos-15-intel
@ -54,6 +58,8 @@ jobs:
cd win32
call build-tcc.bat -t 64 -c cl
echo ::endgroup::
.\tcc -B. ..\win32\test_pe_field_alignment.c -o test_pe_field_alignment.exe && .\test_pe_field_alignment.exe > test_pe_field_alignment.out
fc /n test_pe_field_alignment.out ..\win32\test_pe_field_alignment.ref
.\tcc -I.. libtcc.dll -v ../tests/libtcc_test.c -o libtest.exe && .\libtest.exe
.\tcc -I.. libtcc.dll -run ../tests/libtcc_test.c
@ -100,9 +106,17 @@ jobs:
cd win32
call build-tcc.bat -t arm64 -c clang
echo ::endgroup::
if not exist libtcc.dll exit /b 1
.\tcc -B. -v
.\tcc -B. -run ..\win32\test_run_argv.c alpha "two words" beta > test_run_argv.out
fc /n test_run_argv.out ..\win32\test_run_argv.ref
type ..\win32\test_run_stdin.ref | .\tcc -B. -run ..\win32\test_run_stdin.c > test_run_stdin.out
fc /n test_run_stdin.out ..\win32\test_run_stdin.ref
powershell -NoProfile -Command "$before = @(Get-ChildItem -Path $env:TEMP -Filter 'tcc*.tmp' -Name -ErrorAction SilentlyContinue); & .\tcc -B. -run ..\win32\test_run_exit.c; if ($LASTEXITCODE -ne 27) { exit 1 }; $after = @(Get-ChildItem -Path $env:TEMP -Filter 'tcc*.tmp' -Name -ErrorAction SilentlyContinue); if (Compare-Object $before $after) { Write-Host 'Temporary -run file cleanup mismatch'; Compare-Object $before $after; exit 1 }"
.\tcc -B. ..\win32\test_arm64.c -o test_arm64.exe && .\test_arm64.exe
.\tcc -B. -run ..\examples\ex1.c
clang -O0 -I.. ..\win32\test_arm64_libtcc_context.c ..\win32\test_arm64_libtcc_context.S -o test_libtcc_context.exe
.\test_libtcc_context.exe
.\tcc -B. ..\win32\test_arm64_inline_asm.c -o test_inline_asm.exe && .\test_inline_asm.exe > test_inline_asm.out
> test_inline_asm.expect echo inline asm ok
fc /n test_inline_asm.out test_inline_asm.expect
@ -173,7 +187,10 @@ jobs:
apt-get install -q -y gcc make
run: |
echo "::endgroup::" # flatten 'run container'
./configure && make && make test -k
./configure
make
make test -k
make -C tests/tests2 tests2.73 tests2.109 tests2.121 tests2.133
test-riscv64-linux:
runs-on: ubuntu-22.04

View File

@ -175,6 +175,10 @@ In a script, it gives the following header:
@example
#!/usr/local/bin/tcc -run -L/usr/X11R6/lib -lX11
@end example
On native Windows ARM64 builds, CLI @option{-run} currently uses a
temporary executable for ordinary runs as a compatibility workaround.
@code{libtcc} @code{tcc_run()} remains in-process, and CLI keeps the
in-process path for @option{-dt} and @option{-rstdin}.
@item -v
Display TCC version.

37
tcc.c
View File

@ -234,6 +234,36 @@ static void print_search_dirs(TCCState *s)
static void set_environment(TCCState *s)
{
#ifdef _WIN32
static const char * const names[] = {
"C_INCLUDE_PATH",
"CPATH",
"LIBRARY_PATH",
};
int i;
for (i = 0; i < sizeof(names) / sizeof(names[0]); ++i) {
DWORD len = GetEnvironmentVariableA(names[i], NULL, 0);
char *path;
if (!len)
continue;
path = tcc_malloc(len);
if (!path)
continue;
if (!GetEnvironmentVariableA(names[i], path, len)) {
tcc_free(path);
continue;
}
if (i == 0)
tcc_add_sysinclude_path(s, path);
else if (i == 1)
tcc_add_include_path(s, path);
else
tcc_add_library_path(s, path);
tcc_free(path);
}
#else
char * path;
path = getenv("C_INCLUDE_PATH");
@ -248,6 +278,7 @@ static void set_environment(TCCState *s)
if(path != NULL) {
tcc_add_library_path(s, path);
}
#endif
}
static char *default_outputfile(TCCState *s, const char *first_file)
@ -337,9 +368,6 @@ static int tcc_run_via_temp_exe(TCCState *s, int argc, char **argv)
return tcc_error_noabort("could not get temp directory"), -1;
if (!GetTempFileNameA(tmpdir, "tcc", 0, tmppath))
return tcc_error_noabort("could not create temp file name"), -1;
DeleteFileA(tmppath);
strcpy(tcc_fileextension(tmppath), ".exe");
DeleteFileA(tmppath);
saved_outfile = s->outfile;
saved_output_type = s->output_type;
@ -403,6 +431,9 @@ static int tcc_run_via_temp_exe(TCCState *s, int argc, char **argv)
static int tcc_run_requires_inprocess(const TCCState *s)
{
/* Temporary Windows ARM64 workaround: keep the generic in-process path
where CLI features rely on it, but run ordinary `tcc -run` through a
child process until the native runtime path is fully equivalent. */
return (s->dflag & 16) || s->run_stdin != NULL;
}
#endif

2
tcc.h
View File

@ -592,7 +592,7 @@ typedef struct Section {
typedef struct DLLReference {
int level;
void *handle;
unsigned char found, index;
unsigned char found, index, process_scoped;
char name[1];
} DLLReference;

View File

@ -4226,6 +4226,7 @@ static void struct_layout(CType *type, AttributeDef *ad)
}
/* some individual align was specified */
#ifdef TCC_TARGET_PE
/* GNU aligned(n) on a field is a minimum, not a way to lower alignment. */
if (a > align)
align = a;
#else

43
tccpe.c
View File

@ -29,6 +29,41 @@
#include <sys/stat.h> /* chmod() */
#endif
#if defined(_WIN32) && defined(TCC_IS_NATIVE) && defined(TCC_TARGET_ARM64)
static TCCSem pe_msvcrt_sem;
static HMODULE pe_get_process_msvcrt_handle(void)
{
static HMODULE handle;
HMODULE dll;
wait_sem(&pe_msvcrt_sem);
dll = handle;
if (!dll) {
dll = LoadLibraryA("msvcrt.dll");
if (dll)
handle = dll;
}
post_sem(&pe_msvcrt_sem);
return dll;
}
static HMODULE pe_load_runtime_dll(const char *name, unsigned char *process_scoped)
{
HMODULE dll = NULL;
*process_scoped = 0;
if (0 == PATHCMP(tcc_basename(name), "msvcrt.dll")) {
dll = pe_get_process_msvcrt_handle();
if (dll)
*process_scoped = 1;
}
if (!dll)
dll = LoadLibraryA(name);
return dll;
}
#endif
#ifdef TCC_TARGET_X86_64
# define ADDR3264 ULONGLONG
# define PE_IMAGE_REL IMAGE_REL_BASED_DIR64
@ -995,8 +1030,14 @@ static void pe_build_imports(struct pe_info *pe)
#ifdef TCC_IS_NATIVE
if (pe->type == PE_RUN) {
if (dllref) {
if ( !dllref->handle )
if (!dllref->handle) {
#if defined(_WIN32) && defined(TCC_TARGET_ARM64)
dllref->handle = pe_load_runtime_dll(dllref->name,
&dllref->process_scoped);
#else
dllref->handle = LoadLibraryA(dllref->name);
#endif
}
v = (ADDR3264)GetProcAddress(dllref->handle, ordinal?(char*)0+ordinal:name);
}
if (!v)

View File

@ -181,10 +181,7 @@ ST_FUNC void tcc_run_free(TCCState *s1)
if ( ref->handle )
#ifdef _WIN32
# if defined(__aarch64__)
/* Native ARM64 builds currently host libtcc with the UCRT while
generated PE code still imports msvcrt. Unloading msvcrt from
nested -run states corrupts teardown, so leave it process-wide. */
if (0 == PATHCMP(tcc_basename(ref->name), "msvcrt.dll"))
if (ref->process_scoped)
continue;
# endif
FreeLibrary((HMODULE)ref->handle);
@ -215,20 +212,28 @@ ST_FUNC void tcc_run_free(TCCState *s1)
#ifdef _WIN32
static char **rt_get_environ(void)
{
#ifdef __TINYC__
return NULL;
char **env = NULL;
#ifdef _UCRT
char ***penv = __p__environ();
if (penv)
env = *penv;
#else
return environ;
_get_environ(&env);
#endif
return env;
}
static wchar_t **rt_get_wenviron(void)
{
#ifdef __TINYC__
return NULL;
wchar_t **env = NULL;
#ifdef _UCRT
wchar_t ***penv = __p__wenviron();
if (penv)
env = *penv;
#else
return _wenviron;
_get_wenviron(&env);
#endif
return env;
}
#endif
@ -260,6 +265,8 @@ LIBTCCAPI int tcc_run(TCCState *s1, int argc, char **argv)
#if defined(__APPLE__)
extern char ***_NSGetEnviron(void);
char **envp = *_NSGetEnviron();
#elif defined(_WIN32)
char **envp = rt_get_environ();
#elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__FreeBSD__)
extern char **environ;
char **envp = environ;
@ -1442,6 +1449,23 @@ static PVOID rt_exception_handler;
#if defined(_WIN64) && defined(__aarch64__) && !defined(CONFIG_TCC_BACKTRACE_ONLY)
typedef VOID (__cdecl *rt_restore_context_func_t)(PCONTEXT, struct _EXCEPTION_RECORD *);
#define RT_ARM64_CONTEXT_ASSERT(name, expr) \
typedef char rt_arm64_context_assert_##name[(expr) ? 1 : -1]
RT_ARM64_CONTEXT_ASSERT(size, sizeof(CONTEXT) == 0x390);
RT_ARM64_CONTEXT_ASSERT(flags_offset, offsetof(CONTEXT, ContextFlags) == 0x000);
RT_ARM64_CONTEXT_ASSERT(x_offset, offsetof(CONTEXT, X) == 0x008);
RT_ARM64_CONTEXT_ASSERT(fp_offset, offsetof(CONTEXT, Fp) == 0x0f0);
RT_ARM64_CONTEXT_ASSERT(lr_offset, offsetof(CONTEXT, Lr) == 0x0f8);
RT_ARM64_CONTEXT_ASSERT(sp_offset, offsetof(CONTEXT, Sp) == 0x100);
RT_ARM64_CONTEXT_ASSERT(pc_offset, offsetof(CONTEXT, Pc) == 0x108);
RT_ARM64_CONTEXT_ASSERT(v_offset, offsetof(CONTEXT, V) == 0x110);
RT_ARM64_CONTEXT_ASSERT(v_slot_size, sizeof(((CONTEXT *)0)->V[0]) == 16);
RT_ARM64_CONTEXT_ASSERT(fpcr_offset, offsetof(CONTEXT, Fpcr) == 0x310);
RT_ARM64_CONTEXT_ASSERT(fpsr_offset, offsetof(CONTEXT, Fpsr) == 0x314);
RT_ARM64_CONTEXT_ASSERT(bvr_offset, offsetof(CONTEXT, Bvr) == 0x338);
RT_ARM64_CONTEXT_ASSERT(wvr_offset, offsetof(CONTEXT, Wvr) == 0x380);
#undef RT_ARM64_CONTEXT_ASSERT
static rt_restore_context_func_t rt_get_restore_context_func(void)
{
static rt_restore_context_func_t fn;
@ -1479,7 +1503,7 @@ static void rt_restore_context_from_jmpbuf(void *p_jmp_buf, int code)
ctx.Sp = jb->Sp;
ctx.Pc = jb->Lr;
for (i = 0; i < 8; ++i)
memcpy(&ctx.V[8 + i], &jb->D[i], sizeof(jb->D[i]));
memcpy(&ctx.V[8 + i].D[0], &jb->D[i], sizeof(jb->D[i]));
ctx.Fpcr = jb->Fpcr;
ctx.Fpsr = jb->Fpsr;
fn = rt_get_restore_context_func();

View File

@ -119,7 +119,6 @@ goto :p3
:tarm64
set D=%DARM64%
set P=%PARM64%
set TCC_C=..\tcc.c
goto :p3
:p3

View File

@ -1423,7 +1423,7 @@ typedef DWORD LCID;
typedef PRUNTIME_FUNCTION (*PGET_RUNTIME_FUNCTION_CALLBACK)(DWORD64 ControlPc,PVOID Context);
#ifdef _ARM64_
#if defined(_ARM64_) || defined(__aarch64__)
/* ARM64 Context Definition */
#define CONTEXT_ARM64 0x00400000
@ -1448,26 +1448,83 @@ typedef DWORD LCID;
#define CONTEXT_ALL (CONTEXT_CONTROL | CONTEXT_INTEGER | CONTEXT_FLOATING_POINT | CONTEXT_DEBUG)
#endif
#ifndef _ARM64_CONTEXT_DECLARED
#define _ARM64_CONTEXT_DECLARED
typedef struct _CONTEXT {
DWORD64 ContextFlags;
DWORD64 X[29];
DWORD64 Fp;
DWORD64 Lr;
DWORD64 Sp;
DWORD64 Pc;
DWORD64 V[32];
DWORD Fpcr;
DWORD Fpsr;
DWORD Bcr[8];
DWORD Bvr[8];
DWORD Wcr[2];
DWORD Wvr[2];
} CONTEXT,*PCONTEXT;
#ifndef ARM64_MAX_BREAKPOINTS
#define ARM64_MAX_BREAKPOINTS 8
#endif
#ifndef ARM64_MAX_WATCHPOINTS
#define ARM64_MAX_WATCHPOINTS 2
#endif
#endif /* _ARM64_ */
#ifndef _ARM64_NT_NEON128_DECLARED
#define _ARM64_NT_NEON128_DECLARED
typedef union _ARM64_NT_NEON128 {
struct {
ULONGLONG Low;
LONGLONG High;
} DUMMYSTRUCTNAME;
double D[2];
float S[4];
WORD H[8];
BYTE B[16];
} ARM64_NT_NEON128,*PARM64_NT_NEON128;
#endif
#ifndef _ARM64_CONTEXT_DECLARED
#define _ARM64_CONTEXT_DECLARED
typedef struct DECLSPEC_ALIGN(16) _ARM64_NT_CONTEXT {
ULONG ContextFlags;
ULONG Cpsr;
union {
struct {
DWORD64 X0;
DWORD64 X1;
DWORD64 X2;
DWORD64 X3;
DWORD64 X4;
DWORD64 X5;
DWORD64 X6;
DWORD64 X7;
DWORD64 X8;
DWORD64 X9;
DWORD64 X10;
DWORD64 X11;
DWORD64 X12;
DWORD64 X13;
DWORD64 X14;
DWORD64 X15;
DWORD64 X16;
DWORD64 X17;
DWORD64 X18;
DWORD64 X19;
DWORD64 X20;
DWORD64 X21;
DWORD64 X22;
DWORD64 X23;
DWORD64 X24;
DWORD64 X25;
DWORD64 X26;
DWORD64 X27;
DWORD64 X28;
DWORD64 Fp;
DWORD64 Lr;
} DUMMYSTRUCTNAME;
DWORD64 X[31];
} DUMMYUNIONNAME;
DWORD64 Sp;
DWORD64 Pc;
ARM64_NT_NEON128 V[32];
DWORD Fpcr;
DWORD Fpsr;
DWORD Bcr[ARM64_MAX_BREAKPOINTS];
DWORD64 Bvr[ARM64_MAX_BREAKPOINTS];
DWORD Wcr[ARM64_MAX_WATCHPOINTS];
DWORD64 Wvr[ARM64_MAX_WATCHPOINTS];
} ARM64_NT_CONTEXT,*PARM64_NT_CONTEXT;
#endif
typedef ARM64_NT_CONTEXT CONTEXT,*PCONTEXT;
#endif /* _ARM64_ || __aarch64__ */
typedef DWORD (*POUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK)(HANDLE Process,PVOID TableAddress,PDWORD Entries,PRUNTIME_FUNCTION *Functions);
#define OUT_OF_PROCESS_FUNCTION_TABLE_CALLBACK_EXPORT_NAME "OutOfProcessFunctionTableCallback"

View File

@ -0,0 +1,34 @@
.text
.p2align 2
.global arm64_call_with_dregs
arm64_call_with_dregs:
stp x29, x30, [sp, #-32]!
stp x19, x20, [sp, #16]
mov x29, sp
mov x19, x1
ldr d8, [x0, #0]
ldr d9, [x0, #8]
ldr d10, [x0, #16]
ldr d11, [x0, #24]
ldr d12, [x0, #32]
ldr d13, [x0, #40]
ldr d14, [x0, #48]
ldr d15, [x0, #56]
mov x0, x3
blr x2
str d8, [x19, #0]
str d9, [x19, #8]
str d10, [x19, #16]
str d11, [x19, #24]
str d12, [x19, #32]
str d13, [x19, #40]
str d14, [x19, #48]
str d15, [x19, #56]
ldp x19, x20, [sp, #16]
ldp x29, x30, [sp], #32
ret

View File

@ -0,0 +1,126 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include "libtcc.h"
typedef struct libtcc_api {
TCCState *(*tcc_new)(void);
void (*tcc_delete)(TCCState *s);
void (*tcc_set_lib_path)(TCCState *s, const char *path);
void (*tcc_set_error_func)(TCCState *s, void *opaque, TCCErrorFunc *error_func);
int (*tcc_set_output_type)(TCCState *s, int output_type);
int (*tcc_add_include_path)(TCCState *s, const char *pathname);
int (*tcc_add_library_path)(TCCState *s, const char *pathname);
int (*tcc_compile_string)(TCCState *s, const char *buf);
int (*tcc_run)(TCCState *s, int argc, char **argv);
} libtcc_api;
typedef struct test_context {
libtcc_api api;
} test_context;
extern int arm64_call_with_dregs(const double *expected, double *actual,
int (*fn)(void *opaque), void *opaque);
static const char run_program[] =
"void exit(int);\n"
"int main(int argc, char **argv)\n"
"{\n"
" if (argc != 2)\n"
" return 11;\n"
" if (argv[1][0] != 'x' || argv[1][1] != '\\0')\n"
" return 12;\n"
" exit(42);\n"
"}\n";
static void handle_error(void *opaque, const char *msg)
{
fprintf((FILE *)opaque, "%s\n", msg);
}
static FARPROC load_symbol(HMODULE dll, const char *name)
{
FARPROC proc = GetProcAddress(dll, name);
if (!proc) {
fprintf(stderr, "missing libtcc symbol: %s\n", name);
exit(1);
}
return proc;
}
static int run_once(void *opaque)
{
test_context *ctx = (test_context *)opaque;
TCCState *s;
char *argv[] = { "arm64_libtcc_context", "x" };
int ret;
s = ctx->api.tcc_new();
if (!s) {
fprintf(stderr, "tcc_new failed\n");
return 1;
}
ctx->api.tcc_set_error_func(s, stderr, handle_error);
ctx->api.tcc_set_lib_path(s, ".");
ctx->api.tcc_add_include_path(s, ".\\include");
ctx->api.tcc_add_library_path(s, ".\\lib");
ret = ctx->api.tcc_set_output_type(s, TCC_OUTPUT_MEMORY);
if (!ret)
ret = ctx->api.tcc_compile_string(s, run_program);
if (!ret)
ret = ctx->api.tcc_run(s, 2, argv);
ctx->api.tcc_delete(s);
return ret;
}
int main(void)
{
static const double expected[8] = {
1.25, -2.5, 3.75, -4.875,
5.5, -6.625, 7.75, -8.875
};
double actual[8];
HMODULE dll;
test_context ctx;
int i;
memset(&ctx, 0, sizeof(ctx));
dll = LoadLibraryA("libtcc.dll");
if (!dll) {
fprintf(stderr, "failed to load libtcc.dll\n");
return 1;
}
ctx.api.tcc_new = (void *)load_symbol(dll, "tcc_new");
ctx.api.tcc_delete = (void *)load_symbol(dll, "tcc_delete");
ctx.api.tcc_set_lib_path = (void *)load_symbol(dll, "tcc_set_lib_path");
ctx.api.tcc_set_error_func = (void *)load_symbol(dll, "tcc_set_error_func");
ctx.api.tcc_set_output_type = (void *)load_symbol(dll, "tcc_set_output_type");
ctx.api.tcc_add_include_path = (void *)load_symbol(dll, "tcc_add_include_path");
ctx.api.tcc_add_library_path = (void *)load_symbol(dll, "tcc_add_library_path");
ctx.api.tcc_compile_string = (void *)load_symbol(dll, "tcc_compile_string");
ctx.api.tcc_run = (void *)load_symbol(dll, "tcc_run");
for (i = 0; i < 32; ++i) {
memset(actual, 0, sizeof(actual));
if (arm64_call_with_dregs(expected, actual, run_once, &ctx) != 42) {
fprintf(stderr, "tcc_run did not return 42 on iteration %d\n", i);
FreeLibrary(dll);
return 1;
}
if (memcmp(expected, actual, sizeof(expected)) != 0) {
fprintf(stderr, "nonvolatile FP registers were not restored on iteration %d\n", i);
FreeLibrary(dll);
return 1;
}
}
puts("arm64 libtcc context ok");
FreeLibrary(dll);
return 0;
}

View File

@ -0,0 +1,16 @@
#include <stddef.h>
#include <stdio.h>
struct pe_field_align_min {
char c;
double d __attribute__((aligned(1)));
};
int main(void)
{
printf("size=%u align=%u off=%u\n",
(unsigned)sizeof(struct pe_field_align_min),
(unsigned)__alignof__(struct pe_field_align_min),
(unsigned)offsetof(struct pe_field_align_min, d));
return 0;
}

View File

@ -0,0 +1 @@
size=16 align=8 off=8

11
win32/test_run_argv.c Normal file
View File

@ -0,0 +1,11 @@
#include <stdio.h>
int main(int argc, char **argv)
{
int i;
printf("argc=%d\n", argc);
for (i = 1; i < argc; ++i)
printf("arg%d=<%s>\n", i, argv[i]);
return 0;
}

4
win32/test_run_argv.ref Normal file
View File

@ -0,0 +1,4 @@
argc=4
arg1=<alpha>
arg2=<two words>
arg3=<beta>

6
win32/test_run_exit.c Normal file
View File

@ -0,0 +1,6 @@
#include <stdlib.h>
int main(void)
{
return 27;
}

11
win32/test_run_stdin.c Normal file
View File

@ -0,0 +1,11 @@
#include <stdio.h>
int main(void)
{
char buf[128];
if (!fgets(buf, sizeof(buf), stdin))
return 1;
fputs(buf, stdout);
return 0;
}

1
win32/test_run_stdin.ref Normal file
View File

@ -0,0 +1 @@
temp exe stdin