From f37fbab9150bb5f286e764e5f56c196620069f14 Mon Sep 17 00:00:00 2001 From: Benjamin Oldenburg Date: Mon, 23 Mar 2026 04:19:22 +0700 Subject: [PATCH] Fix ARM64 asm and Windows -run support --- .github/workflows/build.yml | 45 +++- arm64-asm.c | 118 +++++++-- lib/Makefile | 7 +- libtcc.c | 3 + tcc.c | 1 + tcc.h | 1 + tccasm.c | 2 +- tccrun.c | 14 +- tests/asm/test-asm-arm64-ext.c | 26 ++ win32/build-tcc.bat | 18 +- win32/lib/crt1.c | 407 ++++++++++++++++++++++++++---- win32/lib/runmain-arm64.S | 24 ++ win32/lib/runrt.c | 71 ++++++ win32/lib/wincrt1.c | 87 ++++++- win32/test_arm64_libtcc_context.S | 41 ++- win32/test_run_argv.c | 2 + win32/test_run_env.c | 12 + win32/test_run_wargv.c | 14 + win32/test_run_wargv.ref | 4 + win32/test_run_wenv.c | 13 + 20 files changed, 811 insertions(+), 99 deletions(-) create mode 100644 win32/lib/runmain-arm64.S create mode 100644 win32/lib/runrt.c create mode 100644 win32/test_run_env.c create mode 100644 win32/test_run_wargv.c create mode 100644 win32/test_run_wargv.ref create mode 100644 win32/test_run_wenv.c diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 874992da..d3373192 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -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.out + findstr /c:"arg2=" 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.out + findstr /c:"arg2=" 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.out + findstr /c:"arg2=" 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.out + findstr /c:"arg2=" 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 diff --git a/arm64-asm.c b/arm64-asm.c index 3b9a46de..2f04c8e2 100644 --- a/arm64-asm.c +++ b/arm64-asm.c @@ -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; diff --git a/lib/Makefile b/lib/Makefile index 125c6d7c..decde4c7 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -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) : ; diff --git a/libtcc.c b/libtcc.c index 58942e47..d24ec2ce 100644 --- a/libtcc.c +++ b/libtcc.c @@ -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 diff --git a/tcc.c b/tcc.c index a6aef2bd..48526cad 100644 --- a/tcc.c +++ b/tcc.c @@ -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; diff --git a/tcc.h b/tcc.h index 4ecb72d2..630abe92 100644 --- a/tcc.h +++ b/tcc.h @@ -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 */ diff --git a/tccasm.c b/tccasm.c index 8202f7f2..4166995b 100644 --- a/tccasm.c +++ b/tccasm.c @@ -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); diff --git a/tccrun.c b/tccrun.c index 18651dad..ccca4596 100644 --- a/tccrun.c +++ b/tccrun.c @@ -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); diff --git a/tests/asm/test-asm-arm64-ext.c b/tests/asm/test-asm-arm64-ext.c index 737d9da1..3e3097e0 100644 --- a/tests/asm/test-asm-arm64-ext.c +++ b/tests/asm/test-asm-arm64-ext.c @@ -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(); diff --git a/win32/build-tcc.bat b/win32/build-tcc.bat index 107508f7..53ff51a4 100644 --- a/win32/build-tcc.bat +++ b/win32/build-tcc.bat @@ -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 diff --git a/win32/lib/crt1.c b/win32/lib/crt1.c index a105bd39..64d73130 100644 --- a/win32/lib/crt1.c +++ b/win32/lib/crt1.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #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; } // ============================================= diff --git a/win32/lib/runmain-arm64.S b/win32/lib/runmain-arm64.S new file mode 100644 index 00000000..5ca05975 --- /dev/null +++ b/win32/lib/runmain-arm64.S @@ -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) diff --git a/win32/lib/runrt.c b/win32/lib/runrt.c new file mode 100644 index 00000000..46f5d053 --- /dev/null +++ b/win32/lib/runrt.c @@ -0,0 +1,71 @@ +#include +#include + +#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); +} diff --git a/win32/lib/wincrt1.c b/win32/lib/wincrt1.c index eb9dd427..c8ed64d1 100644 --- a/win32/lib/wincrt1.c +++ b/win32/lib/wincrt1.c @@ -5,7 +5,6 @@ #include #include - #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; } diff --git a/win32/test_arm64_libtcc_context.S b/win32/test_arm64_libtcc_context.S index d9f21f14..ffaf4121 100644 --- a/win32/test_arm64_libtcc_context.S +++ b/win32/test_arm64_libtcc_context.S @@ -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 diff --git a/win32/test_run_argv.c b/win32/test_run_argv.c index 0d47295f..f052adf0 100644 --- a/win32/test_run_argv.c +++ b/win32/test_run_argv.c @@ -1,5 +1,7 @@ #include +int _dowildcard = 1; + int main(int argc, char **argv) { int i; diff --git a/win32/test_run_env.c b/win32/test_run_env.c new file mode 100644 index 00000000..ae2b7f77 --- /dev/null +++ b/win32/test_run_env.c @@ -0,0 +1,12 @@ +#include + +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; +} diff --git a/win32/test_run_wargv.c b/win32/test_run_wargv.c new file mode 100644 index 00000000..422e50d6 --- /dev/null +++ b/win32/test_run_wargv.c @@ -0,0 +1,14 @@ +#include +#include + +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; +} diff --git a/win32/test_run_wargv.ref b/win32/test_run_wargv.ref new file mode 100644 index 00000000..6092f200 --- /dev/null +++ b/win32/test_run_wargv.ref @@ -0,0 +1,4 @@ +argc=4 +arg1= +arg2= +arg3= diff --git a/win32/test_run_wenv.c b/win32/test_run_wenv.c new file mode 100644 index 00000000..cf1a2cb3 --- /dev/null +++ b/win32/test_run_wenv.c @@ -0,0 +1,13 @@ +#include +#include + +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; +}