Fix ARM64 asm and Windows -run support

This commit is contained in:
Benjamin Oldenburg 2026-03-23 04:19:22 +07:00
parent 052e40ddd0
commit f37fbab915
20 changed files with 811 additions and 99 deletions

View File

@ -62,6 +62,23 @@ jobs:
cd win32
call build-tcc.bat -t 64 -c cl
echo ::endgroup::
set TCC_RUN_ENV=ok
.\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
.\tcc -B. -run ..\win32\test_run_env.c
> test_run_argv_wc_a.tmp echo a
> test_run_argv_wc_b.tmp echo b
.\tcc -B. -run ..\win32\test_run_argv.c test_run_argv_wc_*.tmp > test_run_argv_wc.out
findstr /c:"argc=3" test_run_argv_wc.out
findstr /c:"arg1=<test_run_argv_wc_a.tmp>" test_run_argv_wc.out
findstr /c:"arg2=<test_run_argv_wc_b.tmp>" test_run_argv_wc.out
.\tcc -B. -run ..\win32\test_run_wargv.c alpha "two words" beta > test_run_wargv.out
fc /n test_run_wargv.out ..\win32\test_run_wargv.ref
.\tcc -B. -run ..\win32\test_run_wenv.c
.\tcc -B. -run ..\win32\test_run_wargv.c test_run_wargv_wc_*.tmp > test_run_wargv_wc.out
findstr /c:"argc=3" test_run_wargv_wc.out
findstr /c:"arg1=<test_run_wargv_wc_a.tmp>" test_run_wargv_wc.out
findstr /c:"arg2=<test_run_wargv_wc_b.tmp>" test_run_wargv_wc.out
.\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
@ -127,9 +144,26 @@ jobs:
call build-tcc.bat -t arm64 -c clang
echo ::endgroup::
if not exist libtcc.dll exit /b 1
set TCC_RUN_ENV=ok
.\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
.\tcc -B. -run ..\win32\test_run_env.c
.\tcc -B. -run ..\win32\test_run_wargv.c alpha "two words" beta > test_run_wargv.out
fc /n test_run_wargv.out ..\win32\test_run_wargv.ref
.\tcc -B. -run ..\win32\test_run_wenv.c
> test_run_argv_wc_a.tmp echo a
> test_run_argv_wc_b.tmp echo b
.\tcc -B. -run ..\win32\test_run_argv.c test_run_argv_wc_*.tmp > test_run_argv_wc.out
findstr /c:"argc=3" test_run_argv_wc.out
findstr /c:"arg1=<test_run_argv_wc_a.tmp>" test_run_argv_wc.out
findstr /c:"arg2=<test_run_argv_wc_b.tmp>" test_run_argv_wc.out
> test_run_wargv_wc_a.tmp echo a
> test_run_wargv_wc_b.tmp echo b
.\tcc -B. -run ..\win32\test_run_wargv.c test_run_wargv_wc_*.tmp > test_run_wargv_wc.out
findstr /c:"argc=3" test_run_wargv_wc.out
findstr /c:"arg1=<test_run_wargv_wc_a.tmp>" test_run_wargv_wc.out
findstr /c:"arg2=<test_run_wargv_wc_b.tmp>" test_run_wargv_wc.out
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 }"
@ -143,14 +177,9 @@ jobs:
.\tcc -B. -DTEST_GOTO ..\win32\test_arm64_inline_asm.c -o test_inline_goto.exe && .\test_inline_goto.exe > test_inline_goto.out
> test_inline_goto.expect echo asm goto ok
fc /n test_inline_goto.out test_inline_goto.expect
.\tcc -B. -DTEST_OPERANDS ..\win32\test_arm64_inline_asm.c -c -o test_inline_operands.obj > test_inline_operands.out 2>&1
if not errorlevel 1 exit /b 1
type test_inline_operands.out
findstr /c:"ARM64 extended inline asm is not implemented" test_inline_operands.out
.\tcc -B. -DTEST_CLOBBERS ..\win32\test_arm64_inline_asm.c -c -o test_inline_clobbers.obj > test_inline_clobbers.out 2>&1
if not errorlevel 1 exit /b 1
type test_inline_clobbers.out
findstr /c:"ARM64 extended inline asm is not implemented" test_inline_clobbers.out
.\tcc -B. -DTEST_OPERANDS ..\win32\test_arm64_inline_asm.c -o test_inline_operands.exe && .\test_inline_operands.exe > test_inline_operands.out
findstr /x /c:"2" test_inline_operands.out
.\tcc -B. -DTEST_CLOBBERS ..\win32\test_arm64_inline_asm.c -o test_inline_clobbers.exe && .\test_inline_clobbers.exe
> test_rstdin.txt echo arm64 stdin
.\tcc -B. -rstdin test_rstdin.txt -run ..\win32\test_rstdin.c > test_rstdin.out
type test_rstdin.out

View File

@ -479,17 +479,45 @@ static void gen_ldst_imm(uint32_t base_opcode, int rt, int rn,
int32_t offset, int size_log2)
{
uint32_t instr = base_opcode;
uint32_t unscaled_opcode = 0;
uint32_t imm12;
if (offset < 0 || (offset & ((1 << size_log2) - 1)))
if (offset >= 0 && !(offset & ((1 << size_log2) - 1))) {
imm12 = offset >> size_log2;
if (imm12 <= 0xFFF) {
instr |= ARM64_IMM12(imm12);
instr |= ARM64_RN(rn);
instr |= ARM64_RT(rt);
emit_instr32(instr);
return;
}
}
switch (base_opcode) {
case ARM64_LDR_X: unscaled_opcode = ARM64_LDUR_X; break;
case ARM64_LDR_W: unscaled_opcode = ARM64_LDUR_W; break;
case ARM64_LDR_B: unscaled_opcode = ARM64_LDUR_B; break;
case ARM64_LDR_H: unscaled_opcode = ARM64_LDUR_H; break;
case ARM64_LDR_D: unscaled_opcode = ARM64_LDUR_D_SIMD; break;
case ARM64_STR_X: unscaled_opcode = ARM64_STUR_X; break;
case ARM64_STR_W: unscaled_opcode = ARM64_STUR_W; break;
case ARM64_STR_B: unscaled_opcode = ARM64_STUR_B; break;
case ARM64_STR_H: unscaled_opcode = ARM64_STUR_H; break;
case ARM64_STR_D: unscaled_opcode = ARM64_STUR_D_SIMD; break;
}
if (unscaled_opcode && offset >= -256 && offset <= 255) {
instr = unscaled_opcode;
instr |= ((uint32_t)offset & 0x1FFU) << 12;
instr |= ARM64_RN(rn);
instr |= ARM64_RT(rt);
emit_instr32(instr);
return;
}
if (offset & ((1 << size_log2) - 1))
tcc_error("invalid load/store offset");
imm12 = offset >> size_log2;
if (imm12 > 0xFFF)
tcc_error("load/store offset out of range");
instr |= ARM64_IMM12(imm12);
instr |= ARM64_RN(rn);
instr |= ARM64_RT(rt);
emit_instr32(instr);
tcc_error("load/store offset out of range");
}
/* Generate STP/LDP (signed immediate) */
@ -826,15 +854,41 @@ static int arm64_memory_is_pair_suitable(const SValue *sv)
return (offset & 7) == 0 && offset >= -512 && offset <= 504;
}
static int arm64_int_reg_is_allocatable(int reg)
{
#ifdef TCC_TARGET_PE
return reg >= TREG_X0 && reg <= TREG_X17;
#else
return reg >= TREG_X0 && reg <= TREG_X30;
#endif
}
static int arm64_memory_needs_address_reg(const SValue *sv)
{
int r;
r = sv->r & ~(VT_BOUNDED | VT_NONCONST);
if (!(r & VT_LVAL))
return 0;
switch (r & VT_VALMASK) {
case VT_LOCAL:
case VT_LLOCAL:
case VT_CONST:
return 1;
}
return 0;
}
static int arm64_prepare_memory_operand(ASMOperand *op, uint8_t *regs_allocated)
{
int reg;
if ((op->vt->r & VT_VALMASK) != VT_LLOCAL)
if (!arm64_memory_needs_address_reg(op->vt))
return 1;
for (reg = 0; reg < 31; reg++) {
if (!(regs_allocated[reg] & REG_IN_MASK)) {
if (arm64_int_reg_is_allocatable(reg)
&& !(regs_allocated[reg] & REG_IN_MASK)) {
regs_allocated[reg] |= REG_IN_MASK;
op->reg = reg;
op->is_memory = 1;
@ -844,6 +898,24 @@ static int arm64_prepare_memory_operand(ASMOperand *op, uint8_t *regs_allocated)
return 0;
}
static void arm64_load_memory_operand_base(int reg, SValue *sv)
{
SValue base;
int rval;
base = *sv;
base.type.t = VT_PTR;
rval = base.r & VT_VALMASK;
if (rval == VT_LLOCAL) {
base.r = (base.r & ~VT_VALMASK) | VT_LOCAL | VT_LVAL;
} else if (rval == VT_CONST || rval == VT_LOCAL) {
base.r &= ~VT_LVAL;
} else {
tcc_internal_error("unsupported ARM64 memory operand base");
}
load(reg, &base);
}
static int operand_is_sp(const Operand *op)
{
return op->reg_tok == TOK_ASM_sp;
@ -1804,8 +1876,11 @@ ST_FUNC void subst_asm_operand(CString *add_str, SValue *sv, int modifier)
size = 1;
else if ((sv->type.t & VT_BTYPE) == VT_SHORT)
size = 2;
else
else if ((sv->type.t & VT_BTYPE) == VT_LLONG ||
(sv->type.t & VT_BTYPE) == VT_PTR)
size = 8;
else
size = 4;
if (modifier == 'x') {
size = 8;
@ -1877,12 +1952,8 @@ ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands,
for (i = 0; i < nb_operands; i++) {
op = &operands[i];
if (op->reg >= 0) {
if ((op->vt->r & VT_VALMASK) == VT_LLOCAL && op->is_memory) {
SValue sv;
sv = *op->vt;
sv.r = (sv.r & ~VT_VALMASK) | VT_LOCAL | VT_LVAL;
sv.type.t = VT_PTR;
load(op->reg, &sv);
if (op->is_memory) {
arm64_load_memory_operand_base(op->reg, op->vt);
} else if (i >= nb_outputs || op->is_rw) {
load(op->reg, op->vt);
}
@ -1892,6 +1963,8 @@ ST_FUNC void asm_gen_code(ASMOperand *operands, int nb_operands,
for (i = 0; i < nb_outputs; i++) {
op = &operands[i];
if (op->reg >= 0) {
if (op->is_memory)
continue;
if ((op->vt->r & VT_VALMASK) == VT_LLOCAL) {
if (!op->is_memory) {
SValue sv;
@ -2026,7 +2099,8 @@ ST_FUNC void asm_compute_constraints(ASMOperand *operands,
goto try_next;
case 'r':
for (reg = 0; reg < 31; reg++) {
if (!is_reg_allocated(reg))
if (arm64_int_reg_is_allocatable(reg)
&& !is_reg_allocated(reg))
goto reg_found;
}
goto try_next;
@ -2116,8 +2190,8 @@ ST_FUNC void asm_compute_constraints(ASMOperand *operands,
goto try_next;
break;
default:
tcc_warning("asm constraint %d ('%s') could not be satisfied",
j, op->constraint);
tcc_error("asm constraint %d ('%s') could not be satisfied",
j, op->constraint);
break;
}
if (op->input_index >= 0) {
@ -2157,15 +2231,13 @@ ST_FUNC void asm_compute_constraints(ASMOperand *operands,
ST_FUNC void asm_clobber(uint8_t *clobber_regs, const char *str)
{
int reg;
TokenSym *ts;
if (!strcmp(str, "memory") ||
!strcmp(str, "cc") ||
!strcmp(str, "flags"))
return;
ts = tok_alloc(str, strlen(str));
reg = arm64_parse_regvar(ts->tok);
reg = arm64_parse_regvar(tok_alloc_const(str));
if (reg == -1)
tcc_error("invalid clobber register '%s'", str);
clobber_regs[reg] = 1;

View File

@ -41,7 +41,7 @@ ARM_O = libtcc1.o armeabi.o armflush.o $(COMMON_O)
ARM64_O = lib-arm64.o $(COMMON_O)
RISCV64_O = lib-arm64.o $(COMMON_O)
COMMON_O = stdatomic.o atomic.o builtin.o alloca.o alloca-bt.o
WIN_O = crt1.o crt1w.o wincrt1.o wincrt1w.o dllcrt1.o dllmain.o
WIN_O = crt1.o crt1w.o wincrt1.o wincrt1w.o dllcrt1.o dllmain.o runrt.o
LIN_O = dsohandle.o
OSX_O =
@ -99,6 +99,11 @@ $(TOP)/bcheck.o : XFLAGS += $(BFLAGS)
$(X)crt1w.o : crt1.c
$(X)wincrt1w.o : wincrt1.c
ifeq ($(T),arm64-win32)
$(TOP)/runmain.o : runmain-arm64.S $(TCC)
$S$(XCC) -c $< -o $@ $(XFLAGS)
endif
# don't try to make it
$(TCC) : ;

View File

@ -905,6 +905,9 @@ LIBTCCAPI TCCState *tcc_new(void)
#ifdef TCC_TARGET_ARM
s->float_abi = ARM_FLOAT_ABI;
#endif
#ifdef TCC_IS_NATIVE
s->run_arg_start = -1;
#endif
#ifdef CONFIG_NEW_DTAGS
s->enable_new_dtags = 1;
#endif

1
tcc.c
View File

@ -330,6 +330,7 @@ redo:
argc = argc0, argv = argv0;
s = s1 = tcc_new();
opt = tcc_parse_args(s, &argc, &argv);
s->run_arg_start = (int)(argv - argv0);
if (n == 0) {
ret = 0;

1
tcc.h
View File

@ -982,6 +982,7 @@ struct TCCState {
#ifdef TCC_IS_NATIVE
const char *run_main; /* entry for tcc_run() */
int run_arg_start; /* argv index for tcc -run relative to the host command line */
void *run_ptr; /* runtime_memory */
unsigned run_size; /* size of runtime_memory */
const char *run_stdin; /* custom stdin file for run_main */

View File

@ -1202,7 +1202,7 @@ static void subst_asm_operands(ASMOperand *operands, int nb_operands,
sv = *op->vt;
if (op->reg >= 0) {
sv.r = op->reg;
if ((op->vt->r & VT_VALMASK) == VT_LLOCAL && op->is_memory)
if (op->is_memory)
sv.r |= VT_LVAL;
}
subst_asm_operand(out_str, &sv, modifier);

View File

@ -241,6 +241,13 @@ static wchar_t **rt_get_wenviron(void)
#endif
return env;
}
static int rt_run_argstart = -1;
static int __cdecl rt_get_run_argstart(void)
{
return rt_run_argstart;
}
#endif
#ifdef _WIN32
@ -288,6 +295,8 @@ LIBTCCAPI int tcc_run(TCCState *s1, int argc, char **argv)
#ifdef _WIN32
tcc_add_symbol(s1, "__rt_get_environ", rt_get_environ);
tcc_add_symbol(s1, "__rt_get_wenviron", rt_get_wenviron);
tcc_add_symbol(s1, "__rt_get_run_argstart", rt_get_run_argstart);
rt_run_argstart = s1->run_arg_start;
#endif
s1->run_main = "_runmain", top_sym = "main";
if (s1->elf_entryname)
@ -296,7 +305,6 @@ LIBTCCAPI int tcc_run(TCCState *s1, int argc, char **argv)
if (tcc_relocate(s1) < 0)
return -1;
prog_main = (void*)get_sym_addr(s1, s1->run_main, 1, 1);
if ((addr_t)-1 == (addr_t)prog_main)
return -1;
@ -318,12 +326,12 @@ LIBTCCAPI int tcc_run(TCCState *s1, int argc, char **argv)
ret = tcc_setjmp(s1, main_jb, tcc_get_symbol(s1, top_sym));
if (0 == ret) {
ret = prog_main(argc, argv, envp);
} else if (RT_EXIT_ZERO == ret) {
} else if (RT_EXIT_ZERO == ret)
ret = 0;
}
#ifdef _WIN32
rt_flush_target_io();
rt_run_argstart = -1;
#endif
fflush(stdout);
fflush(stderr);

View File

@ -70,6 +70,30 @@ void test_memory_store(void)
printf("Test 5 (memory store): PASSED\n");
}
/* Test 5a: Stack memory operand with frame-relative offset */
void test_stack_memory_operand(void)
{
long x = 0;
long val = 321;
asm volatile("str %0, %1" : : "r"(val), "m"(x));
assert(x == 321);
printf("Test 5a (stack memory operand): PASSED\n");
}
/* Test 5b: Symbol memory operand */
static long arm64_symbol_mem;
void test_symbol_memory_operand(void)
{
long val = 654;
arm64_symbol_mem = 0;
asm volatile("str %0, %1" : : "r"(val), "m"(arm64_symbol_mem));
assert(arm64_symbol_mem == 654);
printf("Test 5b (symbol memory operand): PASSED\n");
}
/* Test 6: Clobber list */
void test_clobber_list(void)
{
@ -394,6 +418,8 @@ int main(void)
test_read_write_operand();
test_memory_load();
test_memory_store();
test_stack_memory_operand();
test_symbol_memory_operand();
test_clobber_list();
test_multiple_outputs();
test_constraint_reference();

View File

@ -32,7 +32,7 @@ if (%1)==() goto :p1
:usage
echo usage: build-tcc.bat [ options ... ]
echo options:
echo -c prog use prog (gcc/tcc/cl) to compile tcc
echo -c prog use prog (gcc/clang/tcc/cl) to compile tcc
echo -c "prog options" use prog with options to compile tcc
echo -t 32/64 force 32/64 bit default target
echo -v "version" set tcc version
@ -65,6 +65,12 @@ exit /B 0
if exist %1 rmdir /Q/S %1 && %LOG% %1
exit /B 0
:select_clang
if /I not "%CC%"=="clang" exit /B 0
if "%~1"=="64" if exist C:\msys64\clang64\bin\x86_64-w64-mingw32-clang.exe set CC=C:\msys64\clang64\bin\x86_64-w64-mingw32-clang.exe
if "%~1"=="arm64" if exist C:\msys64\clangarm64\bin\aarch64-w64-mingw32-clang.exe set CC=C:\msys64\clangarm64\bin\aarch64-w64-mingw32-clang.exe
exit /B 0
:cl
@echo off
set CMD=cl
@ -122,6 +128,7 @@ set P=%PARM64%
goto :p3
:p3
call :select_clang %T%
git.exe --version 2>nul
if not %ERRORLEVEL%==0 goto :git_done
for /f %%b in ('git.exe rev-parse --abbrev-ref HEAD') do set GITHASH=%%b
@ -212,18 +219,23 @@ exit /B %ERRORLEVEL%
.\tcc -B. -m%1 -c lib/wincrt1w.c
.\tcc -B. -m%1 -c lib/dllcrt1.c
.\tcc -B. -m%1 -c lib/dllmain.c
.\tcc -B. -m%1 -c lib/runrt.c
.\tcc -B. -m%1 -c lib/chkstk.S
.\tcc -B. -m%1 -c ../lib/alloca.S
.\tcc -B. -m%1 -c ../lib/alloca-bt.S
.\tcc -B. -m%1 -c ../lib/stdatomic.c
.\tcc -B. -m%1 -c ../lib/atomic.S
.\tcc -B. -m%1 -c ../lib/builtin.c
.\tcc -ar lib/%2libtcc1.a libtcc1.o crt1.o crt1w.o wincrt1.o wincrt1w.o dllcrt1.o dllmain.o chkstk.o alloca.o alloca-bt.o stdatomic.o atomic.o builtin.o
.\tcc -ar lib/%2libtcc1.a libtcc1.o crt1.o crt1w.o wincrt1.o wincrt1w.o dllcrt1.o dllmain.o runrt.o chkstk.o alloca.o alloca-bt.o stdatomic.o atomic.o builtin.o
.\tcc -B. -m%1 -c ../lib/bcheck.c -o lib/%2bcheck.o -bt -I..
.\tcc -B. -m%1 -c ../lib/bt-exe.c -o lib/%2bt-exe.o
.\tcc -B. -m%1 -c ../lib/bt-log.c -o lib/%2bt-log.o
.\tcc -B. -m%1 -c ../lib/bt-dll.c -o lib/%2bt-dll.o
.\tcc -B. -m%1 -c ../lib/runmain.c -o lib/%2runmain.o
@if "%1"=="arm64" (
.\tcc -B. -m%1 -c lib/runmain-arm64.S -o lib/%2runmain.o
) else (
.\tcc -B. -m%1 -c ../lib/runmain.c -o lib/%2runmain.o
)
@if "%~2"=="" (
@rem Keep the repo-root runtime helpers in sync for native -run and tests.
if exist tcc.exe copy>nul /y tcc.exe ..\tcc.exe

View File

@ -7,6 +7,8 @@
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#define _UNKNOWN_APP 0
#define _CONSOLE_APP 1
@ -33,6 +35,8 @@
typedef struct { int newmode; } _startupinfo;
int __cdecl __tgetmainargs(int *pargc, _TCHAR ***pargv, _TCHAR ***penv, int globb, _startupinfo*);
int __cdecl __getmainargs(int *pargc, char ***pargv, char ***penv, int globb, _startupinfo*);
int __cdecl __wgetmainargs(int *pargc, wchar_t ***pargv, wchar_t ***penv, int globb, _startupinfo*);
int __cdecl get_tenviron(_TCHAR ***penv);
void __cdecl __set_app_type(int apptype);
unsigned int __cdecl _controlfp(unsigned int new_value, unsigned int mask);
@ -42,6 +46,12 @@ __attribute__((weak)) wchar_t **__cdecl __rt_get_wenviron(void);
#else
__attribute__((weak)) char **__cdecl __rt_get_environ(void);
#endif
__attribute__((weak)) int __cdecl __rt_get_run_argstart(void);
void __tcc_run_on_exit(int ret);
int __tcc_on_exit(void *function, void *arg);
int __tcc_atexit(void (*function)(void));
void __attribute__((noreturn)) __tcc_exit(int code);
#include "crtinit.c"
@ -72,69 +82,368 @@ void _tstart(void)
__tgetmainargs(&__argc, &__targv, &env, _dowildcard, &start_info);
run_ctors(__argc, __targv, env);
ret = _tmain(__argc, __targv, env);
run_dtors();
exit(ret);
__tcc_exit(ret);
}
// =============================================
// for 'tcc -run ,,,'
__attribute__((weak)) void __run_on_exit(int ret)
{
(void)ret;
}
static void run_stdio_init(void)
{
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
static _TCHAR **run_get_tenviron(void)
{
_TCHAR **env = NULL;
_TCHAR **argv = NULL;
int argc;
_startupinfo start_info = {0};
#ifdef UNICODE
if (__rt_get_wenviron)
env = __rt_get_wenviron();
if (!env)
get_tenviron(&env);
#else
if (__rt_get_environ)
env = __rt_get_environ();
if (!env)
get_tenviron(&env);
#endif
if (!env)
__tgetmainargs(&argc, &argv, &env, 0, &start_info);
return env;
}
typedef struct run_targv_state {
int saved_argc;
_TCHAR **saved_argv;
_TCHAR **run_argv;
int active;
} run_targv_state;
static __declspec(thread) run_targv_state *run_targv_tls;
static void free_run_targv(_TCHAR **argv)
{
int i;
if (!argv)
return;
for (i = 0; argv[i]; ++i)
free(argv[i]);
free(argv);
}
static _TCHAR **dup_run_targv_from_tchar(int argc, _TCHAR **argv)
{
int i;
_TCHAR **copy = malloc(sizeof(*copy) * (argc + 1));
if (!copy)
return NULL;
for (i = 0; i < argc; ++i) {
size_t len;
if (!argv[i]) {
copy[i] = NULL;
continue;
}
len = _tcslen(argv[i]) + 1;
copy[i] = malloc(sizeof(*copy[i]) * len);
if (!copy[i])
goto fail;
memcpy(copy[i], argv[i], sizeof(*copy[i]) * len);
}
copy[argc] = NULL;
return copy;
fail:
copy[i] = NULL;
free_run_targv(copy);
return NULL;
}
static _TCHAR **dup_run_targv_from_char(int argc, char **argv)
{
int i;
_TCHAR **copy = malloc(sizeof(*copy) * (argc + 1));
if (!copy)
return NULL;
for (i = 0; i < argc; ++i) {
#ifdef UNICODE
size_t len;
if (!argv[i]) {
copy[i] = NULL;
continue;
}
len = strlen(argv[i]) + 1;
copy[i] = malloc(sizeof(*copy[i]) * len);
if (!copy[i] || (size_t)-1 == mbstowcs(copy[i], argv[i], len))
goto fail;
#else
size_t len;
if (!argv[i]) {
copy[i] = NULL;
continue;
}
len = strlen(argv[i]) + 1;
copy[i] = malloc(len);
if (!copy[i])
goto fail;
memcpy(copy[i], argv[i], len);
#endif
}
copy[argc] = NULL;
return copy;
fail:
copy[i] = NULL;
free_run_targv(copy);
return NULL;
}
typedef LPWSTR *(WINAPI *run_command_line_to_argv_w_func)(LPCWSTR, int *);
static wchar_t **run_command_line_to_argv_w(int *pargc)
{
static run_command_line_to_argv_w_func fn;
static int init;
if (!init) {
HMODULE dll = GetModuleHandleA("shell32.dll");
if (!dll)
dll = LoadLibraryA("shell32.dll");
if (dll)
fn = (run_command_line_to_argv_w_func)(void *)GetProcAddress(dll, "CommandLineToArgvW");
init = 1;
}
return fn ? fn(GetCommandLineW(), pargc) : NULL;
}
static wchar_t *dup_run_wstr(const wchar_t *s)
{
size_t len = wcslen(s) + 1;
wchar_t *copy = malloc(sizeof(*copy) * len);
if (!copy)
return NULL;
memcpy(copy, s, sizeof(*copy) * len);
return copy;
}
static void free_run_wargv(wchar_t **argv)
{
int i;
if (!argv)
return;
for (i = 0; argv[i]; ++i)
free(argv[i]);
free(argv);
}
static int append_run_warg(wchar_t ***pargv, int *pargc, wchar_t *arg)
{
wchar_t **next = realloc(*pargv, sizeof(**pargv) * (*pargc + 2));
if (!next)
return 0;
*pargv = next;
next[*pargc] = arg;
next[*pargc + 1] = NULL;
++*pargc;
return 1;
}
static int run_has_wildcard(const wchar_t *s)
{
for (; *s; ++s)
if (*s == L'*' || *s == L'?')
return 1;
return 0;
}
static int run_expand_wildcard_arg(const wchar_t *pattern, wchar_t ***pargv, int *pargc)
{
WIN32_FIND_DATAW data;
HANDLE h = INVALID_HANDLE_VALUE;
const wchar_t *p;
wchar_t **matches = NULL;
int nmatches = 0, i, j, ok = 0;
size_t prefix_len = 0;
for (p = pattern; *p; ++p) {
if (*p == L'\\' || *p == L'/' || *p == L':')
prefix_len = (size_t)(p - pattern + 1);
}
h = FindFirstFileW(pattern, &data);
if (h == INVALID_HANDLE_VALUE)
return 0;
do {
wchar_t *full;
size_t name_len;
if (data.cFileName[0] == L'.'
&& (!data.cFileName[1] || (data.cFileName[1] == L'.' && !data.cFileName[2])))
continue;
name_len = wcslen(data.cFileName);
full = malloc(sizeof(*full) * (prefix_len + name_len + 1));
if (!full)
goto done;
memcpy(full, pattern, sizeof(*full) * prefix_len);
memcpy(full + prefix_len, data.cFileName, sizeof(*full) * (name_len + 1));
if (!append_run_warg(&matches, &nmatches, full)) {
free(full);
goto done;
}
} while (FindNextFileW(h, &data));
for (i = 0; i + 1 < nmatches; ++i) {
for (j = i + 1; j < nmatches; ++j) {
if (wcscmp(matches[j], matches[i]) < 0) {
wchar_t *tmp = matches[i];
matches[i] = matches[j];
matches[j] = tmp;
}
}
}
for (i = 0; i < nmatches; ++i) {
wchar_t *copy = dup_run_wstr(matches[i]);
if (!copy || !append_run_warg(pargv, pargc, copy)) {
free(copy);
goto done;
}
}
ok = nmatches != 0;
done:
if (h != INVALID_HANDLE_VALUE)
FindClose(h);
free_run_wargv(matches);
return ok;
}
static wchar_t **build_run_wargv(int *prun_argc)
{
wchar_t **cmd_argv = NULL, **run_argv = NULL;
int cmd_argc, run_argc = 0, base, i;
if (!__rt_get_run_argstart)
return NULL;
base = __rt_get_run_argstart();
if (base < 0)
return NULL;
cmd_argv = run_command_line_to_argv_w(&cmd_argc);
if (!cmd_argv || base >= cmd_argc)
goto fail;
for (i = base; i < cmd_argc; ++i) {
wchar_t *copy;
if (_dowildcard && i > base && run_has_wildcard(cmd_argv[i])) {
if (run_expand_wildcard_arg(cmd_argv[i], &run_argv, &run_argc))
continue;
}
copy = dup_run_wstr(cmd_argv[i]);
if (!copy || !append_run_warg(&run_argv, &run_argc, copy)) {
free(copy);
goto fail;
}
}
LocalFree(cmd_argv);
*prun_argc = run_argc;
return run_argv;
fail:
if (cmd_argv)
LocalFree(cmd_argv);
free_run_wargv(run_argv);
return NULL;
}
#ifndef UNICODE
static _TCHAR **dup_run_targv_from_wchar(int argc, wchar_t **argv)
{
int i;
_TCHAR **copy = malloc(sizeof(*copy) * (argc + 1));
if (!copy)
return NULL;
for (i = 0; i < argc; ++i) {
size_t len;
if (!argv[i]) {
copy[i] = NULL;
continue;
}
len = wcstombs(NULL, argv[i], 0);
if ((size_t)-1 == len)
goto fail;
copy[i] = malloc(len + 1);
if (!copy[i] || (size_t)-1 == wcstombs(copy[i], argv[i], len + 1))
goto fail;
}
copy[argc] = NULL;
return copy;
fail:
copy[i] = NULL;
free_run_targv(copy);
return NULL;
}
#endif
static void restore_run_targv(int ret, void *opaque)
{
run_targv_state *state = (run_targv_state *)opaque;
(void)ret;
if (!state || !state->active)
return;
__argc = state->saved_argc;
__targv = state->saved_argv;
free_run_targv(state->run_argv);
state->run_argv = NULL;
state->active = 0;
if (run_targv_tls == state)
run_targv_tls = NULL;
}
static void restore_run_targv_atexit(void)
{
restore_run_targv(0, run_targv_tls);
}
int _runtmain(int argc, /* as tcc passed in */ char **argv)
{
int ret;
_TCHAR **env = NULL;
#ifdef UNICODE
if (__rt_get_wenviron) {
env = __rt_get_wenviron();
#if defined __i386__ || defined __x86_64__
_controlfp(_PC_53, _MCW_PC);
#endif
run_stdio_init();
run_ctors(argc, (_TCHAR **)argv, env);
ret = _tmain(argc, (_TCHAR **)argv, env);
run_dtors();
__run_on_exit(ret);
return ret;
}
#else
if (__rt_get_environ) {
env = __rt_get_environ();
#if defined __i386__ || defined __x86_64__
_controlfp(_PC_53, _MCW_PC);
#endif
run_stdio_init();
run_ctors(argc, (_TCHAR **)argv, env);
ret = _tmain(argc, (_TCHAR **)argv, env);
run_dtors();
__run_on_exit(ret);
return ret;
}
#endif
#ifdef UNICODE
_startupinfo start_info = {0};
int run_argc = argc;
_TCHAR **env = run_get_tenviron();
run_targv_state argv_state = { __argc, __targv, NULL, 0 };
wchar_t **run_wargv = NULL;
__tgetmainargs(&__argc, &__targv, &env, _dowildcard, &start_info);
/* may be wrong when tcc has received wildcards (*.c) */
if (argc < __argc) {
__targv += __argc - argc;
__argc = argc;
}
run_wargv = build_run_wargv(&run_argc);
if (run_wargv) {
#ifdef UNICODE
argv_state.run_argv = dup_run_targv_from_tchar(run_argc, run_wargv);
#else
__argc = argc;
__targv = argv;
get_tenviron(&env);
argv_state.run_argv = dup_run_targv_from_wchar(run_argc, run_wargv);
#endif
free_run_wargv(run_wargv);
}
if (!argv_state.run_argv) {
argv_state.run_argv = dup_run_targv_from_char(argc, argv);
run_argc = argc;
}
if (!argv_state.run_argv)
return 1;
run_targv_tls = &argv_state;
if (__tcc_atexit(restore_run_targv_atexit))
goto fail;
__argc = run_argc;
__targv = argv_state.run_argv;
argv_state.active = 1;
#if defined __i386__ || defined __x86_64__
_controlfp(_PC_53, _MCW_PC);
#endif
@ -142,8 +451,12 @@ int _runtmain(int argc, /* as tcc passed in */ char **argv)
run_ctors(__argc, __targv, env);
ret = _tmain(__argc, __targv, env);
run_dtors();
__run_on_exit(ret);
__tcc_run_on_exit(ret);
return ret;
fail:
run_targv_tls = NULL;
free_run_targv(argv_state.run_argv);
return 1;
}
// =============================================

24
win32/lib/runmain-arm64.S Normal file
View File

@ -0,0 +1,24 @@
#ifdef __leading_underscore
# define _(s) _##s
#else
# define _(s) s
#endif
.text
.p2align 2
.global _(__run_on_exit)
_(__run_on_exit):
b _(__tcc_run_on_exit)
.global _(on_exit)
_(on_exit):
b _(__tcc_on_exit)
.global _(atexit)
_(atexit):
b _(__tcc_atexit)
.global _(exit)
_(exit):
b _(__tcc_exit)

71
win32/lib/runrt.c Normal file
View File

@ -0,0 +1,71 @@
#include <stdio.h>
#include <stdlib.h>
#ifdef __leading_underscore
# define _(s) s
#else
# define _(s) _##s
#endif
extern void (*_(_fini_array_start)[]) (void);
extern void (*_(_fini_array_end)[]) (void);
typedef struct rt_frame {
void *ip, *fp, *sp;
} rt_frame;
__attribute__((weak, noreturn)) void __rt_exit(rt_frame *, int);
static void *rt_exitfunc[32];
static void *rt_exitarg[32];
static int __rt_nr_exit;
static void run_dtors(void)
{
int i = 0;
while (&_(_fini_array_end)[i] != _(_fini_array_start))
(*_(_fini_array_end)[--i])();
}
void __tcc_run_on_exit(int ret)
{
int n = __rt_nr_exit;
while (n)
--n, ((void (*)(int, void *))rt_exitfunc[n])(ret, rt_exitarg[n]);
}
int __tcc_on_exit(void *function, void *arg)
{
int n = __rt_nr_exit;
if (n < 32) {
rt_exitfunc[n] = function;
rt_exitarg[n] = arg;
__rt_nr_exit = n + 1;
return 0;
}
return 1;
}
int __tcc_atexit(void (*function)(void))
{
return __tcc_on_exit(function, 0);
}
void __attribute__((noreturn)) __tcc_exit(int code)
{
rt_frame f;
run_dtors();
__tcc_run_on_exit(code);
if (__rt_exit) {
f.fp = 0;
f.ip = __tcc_exit;
f.sp = 0;
__rt_exit(&f, code);
}
fflush(NULL);
_exit(code);
}

View File

@ -5,7 +5,6 @@
#include <windows.h>
#include <stdlib.h>
#define __UNKNOWN_APP 0
#define __CONSOLE_APP 1
#define __GUI_APP 2
@ -27,10 +26,54 @@ int APIENTRY wWinMain(HINSTANCE, HINSTANCE, LPWSTR, int);
typedef struct { int newmode; } _startupinfo;
int __cdecl __tgetmainargs(int *pargc, _TCHAR ***pargv, _TCHAR ***penv, int globb, _startupinfo*);
int __cdecl __getmainargs(int *pargc, char ***pargv, char ***penv, int globb, _startupinfo*);
int __cdecl __wgetmainargs(int *pargc, wchar_t ***pargv, wchar_t ***penv, int globb, _startupinfo*);
int __cdecl get_tenviron(_TCHAR ***penv);
__attribute__((weak)) int __cdecl __rt_get_run_argstart(void);
#include "crtinit.c"
static int select_run_arg_start_t(int base, const _TCHAR *arg0, int full_argc, _TCHAR **full_argv)
{
int i;
if (base < 0 || base > full_argc)
return -1;
if (!arg0)
return base;
for (i = base; i < full_argc; ++i) {
if (full_argv[i] && 0 == _tcscmp(full_argv[i], arg0))
return i;
}
return base;
}
static int find_run_arg_slice(int globb, int *pstart, int *prun_argc)
{
int literal_argc, full_argc, base, start, ret = 0;
_TCHAR **literal_argv = NULL, **full_argv = NULL;
_startupinfo start_info = {0};
if (!__rt_get_run_argstart)
return 0;
base = __rt_get_run_argstart();
if (base < 0)
return 0;
if (__tgetmainargs(&literal_argc, &literal_argv, NULL, 0, &start_info))
return 0;
if (base >= literal_argc)
return 0;
if (__tgetmainargs(&full_argc, &full_argv, NULL, globb, &start_info))
return 0;
start = select_run_arg_start_t(base, literal_argv[base], full_argc, full_argv);
if (start < 0 || start > full_argc)
return 0;
*pstart = start;
*prun_argc = full_argc - start;
ret = 1;
return ret;
}
static int go_winmain(TCHAR *arg1)
{
STARTUPINFO si;
@ -78,14 +121,42 @@ int _twinstart(void)
int _runtwinmain(int argc, /* as tcc passed in */ char **argv)
{
int saved_argc = __argc;
_TCHAR **saved_argv = __targv;
int ret;
int run_arg_start = -1;
#ifdef UNICODE
_startupinfo start_info = {0};
__tgetmainargs(&__argc, &__targv, NULL, 0, &start_info);
/* may be wrong when tcc has received wildcards (*.c) */
if (argc < __argc)
__targv += __argc - argc, __argc = argc;
{
int full_argc;
_TCHAR **full_argv = NULL;
_startupinfo start_info = {0};
if (find_run_arg_slice(0, &run_arg_start, &__argc)
&& !__tgetmainargs(&full_argc, &full_argv, NULL, 0, &start_info)
&& run_arg_start <= full_argc)
__targv = full_argv + run_arg_start;
else {
__tgetmainargs(&__argc, &__targv, NULL, 0, &start_info);
if (argc < __argc)
__targv += __argc - argc, __argc = argc;
}
}
#else
__argc = argc, __targv = argv;
{
int full_argc;
_TCHAR **full_argv = NULL;
_startupinfo start_info = {0};
if (find_run_arg_slice(0, &run_arg_start, &__argc)
&& !__tgetmainargs(&full_argc, &full_argv, NULL, 0, &start_info)
&& run_arg_start <= full_argc)
__targv = full_argv + run_arg_start;
else
__argc = argc, __targv = argv;
}
#endif
return go_winmain(__argc > 1 ? __targv[1] : NULL);
ret = go_winmain(__argc > 1 ? __targv[1] : NULL);
__argc = saved_argc;
__targv = saved_argv;
return ret;
}

View File

@ -3,9 +3,30 @@
.global arm64_call_with_dregs
arm64_call_with_dregs:
stp x29, x30, [sp, -32]!
stp x19, x20, [sp, 16]
mov x29, sp
.seh_proc arm64_call_with_dregs
sub sp, sp, 80
.seh_stackalloc 80
str x30, [sp, 0]
.seh_save_reg x30, 0
str x19, [sp, 8]
.seh_save_reg x19, 8
str d8, [sp, 16]
.seh_save_freg d8, 16
str d9, [sp, 24]
.seh_save_freg d9, 24
str d10, [sp, 32]
.seh_save_freg d10, 32
str d11, [sp, 40]
.seh_save_freg d11, 40
str d12, [sp, 48]
.seh_save_freg d12, 48
str d13, [sp, 56]
.seh_save_freg d13, 56
str d14, [sp, 64]
.seh_save_freg d14, 64
str d15, [sp, 72]
.seh_save_freg d15, 72
.seh_endprologue
mov x19, x1
ldr d8, [x0, 0]
@ -29,6 +50,16 @@ arm64_call_with_dregs:
str d14, [x19, 48]
str d15, [x19, 56]
ldp x19, x20, [sp, 16]
ldp x29, x30, [sp], 32
ldr d8, [sp, 16]
ldr d9, [sp, 24]
ldr d10, [sp, 32]
ldr d11, [sp, 40]
ldr d12, [sp, 48]
ldr d13, [sp, 56]
ldr d14, [sp, 64]
ldr d15, [sp, 72]
ldr x19, [sp, 8]
ldr x30, [sp, 0]
add sp, sp, 80
ret
.seh_endproc

View File

@ -1,5 +1,7 @@
#include <stdio.h>
int _dowildcard = 1;
int main(int argc, char **argv)
{
int i;

12
win32/test_run_env.c Normal file
View File

@ -0,0 +1,12 @@
#include <stdio.h>
int main(int argc, char **argv, char **envp)
{
(void)argc;
(void)argv;
if (!envp || !envp[0]) {
fputs("missing envp\n", stderr);
return 1;
}
return 0;
}

14
win32/test_run_wargv.c Normal file
View File

@ -0,0 +1,14 @@
#include <stdio.h>
#include <wchar.h>
int _dowildcard = 1;
int wmain(int argc, wchar_t **argv)
{
int i;
printf("argc=%d\n", argc);
for (i = 1; i < argc; ++i)
printf("arg%d=<%ls>\n", i, argv[i]);
return 0;
}

4
win32/test_run_wargv.ref Normal file
View File

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

13
win32/test_run_wenv.c Normal file
View File

@ -0,0 +1,13 @@
#include <stdio.h>
#include <wchar.h>
int wmain(int argc, wchar_t **argv, wchar_t **envp)
{
(void)argc;
(void)argv;
if (!envp || !envp[0]) {
fputws(L"missing envp\n", stderr);
return 1;
}
return 0;
}