From 2bcab3c78f5edad219ae84837c3e054c4c91c1fb Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Wed, 15 Apr 2026 15:48:59 +0300 Subject: [PATCH] cgen: fix c error while compiling freestanding v program (fixes #25567) --- vlib/builtin/linux_bare/linux_syscalls.i386.v | 264 +++++++++++++ vlib/builtin/linux_bare/linux_syscalls.v | 364 +++++++++--------- vlib/v/gen/c/cheaders.v | 2 +- vlib/v/gen/c/freestanding_i386_test.v | 32 ++ vlib/v/help/build/build-c.txt | 3 + vlib/v/pref/pref.v | 4 + vlib/v/pref/pref_test.v | 16 + 7 files changed, 503 insertions(+), 182 deletions(-) create mode 100644 vlib/builtin/linux_bare/linux_syscalls.i386.v create mode 100644 vlib/v/gen/c/freestanding_i386_test.v diff --git a/vlib/builtin/linux_bare/linux_syscalls.i386.v b/vlib/builtin/linux_bare/linux_syscalls.i386.v new file mode 100644 index 000000000..71917884a --- /dev/null +++ b/vlib/builtin/linux_bare/linux_syscalls.i386.v @@ -0,0 +1,264 @@ +module builtin + +struct LinuxMmapArgs { + addr usize + len usize + prot usize + flags usize + fd usize + offset usize +} + +fn split_int_errno(rc_in usize) (i64, Errno) { + rc := isize(rc_in) + if rc < 0 { + return i64(-1), unsafe { Errno(-rc) } + } + return i64(rc), Errno.enoerror +} + +// 3 sys_read +fn sys_read(fd i64, buf &u8, count u64) (i64, Errno) { + return split_int_errno(sys_call3(3, usize(fd), usize(buf), usize(count))) +} + +// 4 sys_write +pub fn sys_write(fd i64, buf &u8, count u64) (i64, Errno) { + return split_int_errno(sys_call3(4, usize(fd), usize(buf), usize(count))) +} + +// 5 sys_open +fn sys_open(filename &u8, flags i64, mode int) (i64, Errno) { + return split_int_errno(sys_call3(5, usize(filename), usize(flags), usize(mode))) +} + +// 6 sys_close +fn sys_close(fd i64) Errno { + return unsafe { Errno(-isize(sys_call1(6, usize(fd)))) } +} + +// 90 sys_mmap +fn sys_mmap(addr &u8, len u64, prot MemProt, flags MapFlags, fildes i64, off u64) (&u8, Errno) { + args := LinuxMmapArgs{ + addr: usize(addr) + len: usize(len) + prot: usize(prot) + flags: usize(flags) + fd: usize(fildes) + offset: usize(off) + } + rc := sys_call1(90, usize(&args)) + a, e := split_int_errno(rc) + return unsafe { &u8(usize(a)) }, e +} + +// 91 sys_munmap +fn sys_munmap(addr voidptr, len u64) Errno { + return unsafe { Errno(-isize(sys_call2(91, usize(addr), usize(len)))) } +} + +// 163 sys_mremap +fn sys_mremap(old_addr voidptr, old_len u64, new_len u64, flags u64) (&u8, Errno) { + rc := sys_call4(163, usize(old_addr), usize(old_len), usize(new_len), usize(flags)) + a, e := split_int_errno(rc) + return unsafe { &u8(usize(a)) }, e +} + +// 42 sys_pipe +fn sys_pipe(filedes &int) Errno { + return unsafe { Errno(isize(sys_call1(42, usize(filedes)))) } +} + +// 158 sys_sched_yield +fn sys_sched_yield() Errno { + return unsafe { Errno(isize(sys_call0(158))) } +} + +// 219 sys_madvise +fn sys_madvise(addr voidptr, len u64, advice int) Errno { + return unsafe { Errno(isize(sys_call3(219, usize(addr), usize(len), usize(advice)))) } +} + +// 20 sys_getpid +fn sys_getpid() int { + return int(sys_call0(20)) +} + +// 2 sys_fork +fn sys_fork() int { + return int(sys_call0(2)) +} + +// 190 sys_vfork +fn sys_vfork() int { + return int(sys_call0(190)) +} + +// 63 sys_dup2 +fn sys_dup2(oldfd int, newfd int) (i64, Errno) { + return split_int_errno(sys_call2(63, usize(oldfd), usize(newfd))) +} + +// 11 sys_execve +fn sys_execve(filename &u8, argv []&u8, envp []&u8) int { + return int(sys_call3(11, usize(filename), usize(argv.data), usize(envp.data))) +} + +// 1 sys_exit +@[noreturn] +fn sys_exit(ec int) { + sys_call1(1, usize(ec)) + for {} +} + +// 199 sys_getuid32 +fn sys_getuid() int { + return int(sys_call0(199)) +} + +// 284 sys_waitid +fn sys_waitid(which WiWhich, pid int, infop &int, options int, ru voidptr) Errno { + return unsafe { + Errno(isize(sys_call5(284, usize(which), usize(pid), usize(infop), usize(options), + usize(ru)))) + } +} + +fn sys_call0(scn usize) usize { + mut res := usize(0) + asm volatile i386 { + mov eax, scn + int 0x80 + mov res, eax + ; =r (res) + ; r (scn) + ; eax + memory + } + return res +} + +fn sys_call1(scn usize, arg1 usize) usize { + mut res := usize(0) + asm volatile i386 { + mov eax, scn + mov ebx, arg1 + int 0x80 + mov res, eax + ; =r (res) + ; r (scn) + r (arg1) + ; eax + ebx + memory + } + return res +} + +fn sys_call2(scn usize, arg1 usize, arg2 usize) usize { + mut res := usize(0) + asm volatile i386 { + mov eax, scn + mov ebx, arg1 + mov ecx, arg2 + int 0x80 + mov res, eax + ; =r (res) + ; r (scn) + r (arg1) + r (arg2) + ; eax + ebx + ecx + memory + } + return res +} + +fn sys_call3(scn usize, arg1 usize, arg2 usize, arg3 usize) usize { + mut res := usize(0) + asm volatile i386 { + mov eax, scn + mov ebx, arg1 + mov ecx, arg2 + mov edx, arg3 + int 0x80 + mov res, eax + ; =r (res) + ; r (scn) + r (arg1) + r (arg2) + r (arg3) + ; eax + ebx + ecx + edx + memory + } + return res +} + +fn sys_call4(scn usize, arg1 usize, arg2 usize, arg3 usize, arg4 usize) usize { + mut res := usize(0) + asm volatile i386 { + mov eax, scn + mov ebx, arg1 + mov ecx, arg2 + mov edx, arg3 + mov esi, arg4 + int 0x80 + mov res, eax + ; =r (res) + ; r (scn) + r (arg1) + r (arg2) + r (arg3) + r (arg4) + ; eax + ebx + ecx + edx + esi + memory + } + return res +} + +fn sys_call5(scn usize, arg1 usize, arg2 usize, arg3 usize, arg4 usize, arg5 usize) usize { + mut res := usize(0) + asm volatile i386 { + mov eax, scn + mov ebx, arg1 + mov ecx, arg2 + mov edx, arg3 + mov esi, arg4 + mov edi, arg5 + int 0x80 + mov res, eax + ; =r (res) + ; r (scn) + r (arg1) + r (arg2) + r (arg3) + r (arg4) + r (arg5) + ; eax + ebx + ecx + edx + esi + edi + memory + } + return res +} + +asm i386 { + .globl _start + _start: + call main + mov eax, 1 + xor ebx, ebx + int 0x80 + ret +} diff --git a/vlib/builtin/linux_bare/linux_syscalls.v b/vlib/builtin/linux_bare/linux_syscalls.v index 9c53f1bd8..13a7e5fda 100644 --- a/vlib/builtin/linux_bare/linux_syscalls.v +++ b/vlib/builtin/linux_bare/linux_syscalls.v @@ -223,221 +223,223 @@ enum WiSiCode { cld_continued = 6 // stopped child has continued } -fn split_int_errno(rc_in u64) (i64, Errno) { - rc := i64(rc_in) - if rc < 0 { - return i64(-1), unsafe { Errno(-rc) } +$if !i386 { + fn split_int_errno(rc_in u64) (i64, Errno) { + rc := i64(rc_in) + if rc < 0 { + return i64(-1), unsafe { Errno(-rc) } + } + return rc, Errno.enoerror } - return rc, Errno.enoerror -} -// 0 sys_read -fn sys_read(fd i64, buf &u8, count u64) (i64, Errno) { - return split_int_errno(sys_call3(0, u64(fd), u64(buf), count)) -} + // 0 sys_read + fn sys_read(fd i64, buf &u8, count u64) (i64, Errno) { + return split_int_errno(sys_call3(0, u64(fd), u64(buf), count)) + } -// 1 sys_write -pub fn sys_write(fd i64, buf &u8, count u64) (i64, Errno) { - return split_int_errno(sys_call3(1, u64(fd), u64(buf), count)) -} + // 1 sys_write + pub fn sys_write(fd i64, buf &u8, count u64) (i64, Errno) { + return split_int_errno(sys_call3(1, u64(fd), u64(buf), count)) + } -// 2 sys_open -fn sys_open(filename &u8, flags i64, mode int) (i64, Errno) { - return split_int_errno(sys_call3(2, u64(filename), u64(flags), u64(mode))) -} + // 2 sys_open + fn sys_open(filename &u8, flags i64, mode int) (i64, Errno) { + return split_int_errno(sys_call3(2, u64(filename), u64(flags), u64(mode))) + } -// 3 sys_close -fn sys_close(fd i64) Errno { - return unsafe { Errno(-i64(sys_call1(3, u64(fd)))) } -} + // 3 sys_close + fn sys_close(fd i64) Errno { + return unsafe { Errno(-i64(sys_call1(3, u64(fd)))) } + } -// 9 sys_mmap -fn sys_mmap(addr &u8, len u64, prot MemProt, flags MapFlags, fildes i64, off u64) (&u8, Errno) { - rc := sys_call6(9, u64(addr), len, u64(prot), u64(flags), fildes, off) - a, e := split_int_errno(rc) - return unsafe { &u8(a) }, e -} + // 9 sys_mmap + fn sys_mmap(addr &u8, len u64, prot MemProt, flags MapFlags, fildes i64, off u64) (&u8, Errno) { + rc := sys_call6(9, u64(addr), len, u64(prot), u64(flags), fildes, off) + a, e := split_int_errno(rc) + return unsafe { &u8(a) }, e + } -// 11 sys_munmap -fn sys_munmap(addr voidptr, len u64) Errno { - return unsafe { Errno(-sys_call2(11, u64(addr), len)) } -} + // 11 sys_munmap + fn sys_munmap(addr voidptr, len u64) Errno { + return unsafe { Errno(-sys_call2(11, u64(addr), len)) } + } -// 25 sys_mremap -fn sys_mremap(old_addr voidptr, old_len u64, new_len u64, flags u64) (&u8, Errno) { - rc := sys_call4(25, u64(old_addr), old_len, new_len, flags) - a, e := split_int_errno(rc) - return unsafe { &u8(a) }, e -} + // 25 sys_mremap + fn sys_mremap(old_addr voidptr, old_len u64, new_len u64, flags u64) (&u8, Errno) { + rc := sys_call4(25, u64(old_addr), old_len, new_len, flags) + a, e := split_int_errno(rc) + return unsafe { &u8(a) }, e + } -// 22 sys_pipe -fn sys_pipe(filedes &int) Errno { - return unsafe { Errno(sys_call1(22, u64(filedes))) } -} + // 22 sys_pipe + fn sys_pipe(filedes &int) Errno { + return unsafe { Errno(sys_call1(22, u64(filedes))) } + } -// 24 sys_sched_yield -fn sys_sched_yield() Errno { - return unsafe { Errno(sys_call0(24)) } -} + // 24 sys_sched_yield + fn sys_sched_yield() Errno { + return unsafe { Errno(sys_call0(24)) } + } -// 28 sys_madvise -fn sys_madvise(addr voidptr, len u64, advice int) Errno { - return unsafe { Errno(sys_call3(28, u64(addr), len, u64(advice))) } -} + // 28 sys_madvise + fn sys_madvise(addr voidptr, len u64, advice int) Errno { + return unsafe { Errno(sys_call3(28, u64(addr), len, u64(advice))) } + } -// 39 sys_getpid -fn sys_getpid() int { - return int(sys_call0(39)) -} + // 39 sys_getpid + fn sys_getpid() int { + return int(sys_call0(39)) + } -// 57 sys_fork -fn sys_fork() int { - return int(sys_call0(57)) -} + // 57 sys_fork + fn sys_fork() int { + return int(sys_call0(57)) + } -// 58 sys_vfork -fn sys_vfork() int { - return int(sys_call0(58)) -} + // 58 sys_vfork + fn sys_vfork() int { + return int(sys_call0(58)) + } -// 33 sys_dup2 -fn sys_dup2(oldfd int, newfd int) (i64, Errno) { - return split_int_errno(sys_call2(33, u64(oldfd), u64(newfd))) -} + // 33 sys_dup2 + fn sys_dup2(oldfd int, newfd int) (i64, Errno) { + return split_int_errno(sys_call2(33, u64(oldfd), u64(newfd))) + } -// 59 sys_execve -fn sys_execve(filename &u8, argv []&u8, envp []&u8) int { - return int(sys_call3(59, u64(filename), u64(argv.data), u64(envp.data))) -} + // 59 sys_execve + fn sys_execve(filename &u8, argv []&u8, envp []&u8) int { + return int(sys_call3(59, u64(filename), u64(argv.data), u64(envp.data))) + } -// 60 sys_exit -@[noreturn] -fn sys_exit(ec int) { - sys_call1(60, u64(ec)) - for {} -} + // 60 sys_exit + @[noreturn] + fn sys_exit(ec int) { + sys_call1(60, u64(ec)) + for {} + } -// 102 sys_getuid -fn sys_getuid() int { - return int(sys_call0(102)) -} + // 102 sys_getuid + fn sys_getuid() int { + return int(sys_call0(102)) + } -// 247 sys_waitid -fn sys_waitid(which WiWhich, pid int, infop &int, options int, ru voidptr) Errno { - return unsafe { - Errno(sys_call5(247, u64(which), u64(pid), u64(infop), u64(options), u64(ru))) + // 247 sys_waitid + fn sys_waitid(which WiWhich, pid int, infop &int, options int, ru voidptr) Errno { + return unsafe { + Errno(sys_call5(247, u64(which), u64(pid), u64(infop), u64(options), u64(ru))) + } } -} -fn sys_call0(scn u64) u64 { - mut res := u64(0) - asm amd64 { - syscall - ; =a (res) - ; 0 (scn) + fn sys_call0(scn u64) u64 { + mut res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; 0 (scn) + } + return res } - return res -} -fn sys_call1(scn u64, arg1 u64) u64 { - mut res := u64(0) - asm amd64 { - syscall - ; =a (res) - ; 0 (scn) - D (arg1) + fn sys_call1(scn u64, arg1 u64) u64 { + mut res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; 0 (scn) + D (arg1) + } + return res } - return res -} -fn sys_call2(scn u64, arg1 u64, arg2 u64) u64 { - mut res := u64(0) - asm amd64 { - syscall - ; =a (res) - ; 0 (scn) - D (arg1) - S (arg2) + fn sys_call2(scn u64, arg1 u64, arg2 u64) u64 { + mut res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; 0 (scn) + D (arg1) + S (arg2) + } + return res } - return res -} -fn sys_call3(scn u64, arg1 u64, arg2 u64, arg3 u64) u64 { - mut res := u64(0) - asm amd64 { - syscall - ; =a (res) - ; 0 (scn) - D (arg1) - S (arg2) - d (arg3) + fn sys_call3(scn u64, arg1 u64, arg2 u64, arg3 u64) u64 { + mut res := u64(0) + asm amd64 { + syscall + ; =a (res) + ; 0 (scn) + D (arg1) + S (arg2) + d (arg3) + } + return res } - return res -} -fn sys_call4(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64) u64 { - mut res := u64(0) - asm amd64 { - mov r10, arg4 - syscall - ; =a (res) - ; 0 (scn) - D (arg1) - S (arg2) - d (arg3) - g (arg4) - ; r10 + fn sys_call4(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64) u64 { + mut res := u64(0) + asm amd64 { + mov r10, arg4 + syscall + ; =a (res) + ; 0 (scn) + D (arg1) + S (arg2) + d (arg3) + g (arg4) + ; r10 + } + return res } - return res -} -fn sys_call5(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 u64) u64 { - mut res := u64(0) - asm amd64 { - mov r10, arg4 - mov r8, arg5 - syscall - ; =a (res) - ; 0 (scn) - D (arg1) - S (arg2) - d (arg3) - g (arg4) - g (arg5) - ; r10 - r8 + fn sys_call5(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 u64) u64 { + mut res := u64(0) + asm amd64 { + mov r10, arg4 + mov r8, arg5 + syscall + ; =a (res) + ; 0 (scn) + D (arg1) + S (arg2) + d (arg3) + g (arg4) + g (arg5) + ; r10 + r8 + } + return res + } + + fn sys_call6(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 i64, arg6 u64) u64 { + mut res := u64(0) + asm amd64 { + mov r10, arg4 + mov r8, arg5 + mov r9, arg6 + syscall + ; =a (res) + ; 0 (scn) + D (arg1) + S (arg2) + d (arg3) + g (arg4) + g (arg5) + g (arg6) + ; r10 + r8 + r9 + } + return res } - return res -} -fn sys_call6(scn u64, arg1 u64, arg2 u64, arg3 u64, arg4 u64, arg5 i64, arg6 u64) u64 { - mut res := u64(0) asm amd64 { - mov r10, arg4 - mov r8, arg5 - mov r9, arg6 + .globl _start + _start: + call main + mov rax, 60 + xor rdi, rdi syscall - ; =a (res) - ; 0 (scn) - D (arg1) - S (arg2) - d (arg3) - g (arg4) - g (arg5) - g (arg6) - ; r10 - r8 - r9 + ret } - return res -} - -asm amd64 { - .globl _start - _start: - call main - mov rax, 60 - xor rdi, rdi - syscall - ret } diff --git a/vlib/v/gen/c/cheaders.v b/vlib/v/gen/c/cheaders.v index f5cd68aa0..f8384bf29 100644 --- a/vlib/v/gen/c/cheaders.v +++ b/vlib/v/gen/c/cheaders.v @@ -881,7 +881,7 @@ void _vcleanup(); #endif #define sigaction_size sizeof(sigaction); #define _ARR_LEN(a) ( (sizeof(a)) / (sizeof(a[0])) ) -voidptr builtin__memdup(voidptr src, isize size); +VV_LOC voidptr builtin__memdup(voidptr src, isize size); ' const c_wyhash_headers = ' diff --git a/vlib/v/gen/c/freestanding_i386_test.v b/vlib/v/gen/c/freestanding_i386_test.v new file mode 100644 index 000000000..f2bb23432 --- /dev/null +++ b/vlib/v/gen/c/freestanding_i386_test.v @@ -0,0 +1,32 @@ +module c + +import os + +const test_vexe = os.quoted_path(@VEXE) + +fn test_freestanding_i386_c_output_uses_int80_and_static_memdup_decl() { + workdir := os.join_path(os.vtmp_dir(), 'v_freestanding_i386_${os.getpid()}') + os.mkdir_all(workdir)! + defer { + os.rmdir_all(workdir) or {} + } + main_v := os.join_path(workdir, 'main.v') + out_c := os.join_path(workdir, 'main.c') + os.write_file(main_v, "module main + +@[export: 'kmain'] +pub fn kmain() { + for {} +} +")! + cmd := '${test_vexe} -gc none -no-skip-unused -freestanding -no-std -is_o -m32 -o ${os.quoted_path(out_c)} ${os.quoted_path(main_v)}' + res := os.execute(cmd) + assert res.exit_code == 0, '${cmd}\n${res.output}' + generated := os.read_file(out_c)! + assert generated.contains('VV_LOC voidptr builtin__memdup(voidptr src, isize size);') + assert !generated.contains('\nvoidptr builtin__memdup(voidptr src, isize size);\n') + assert generated.contains('int $0x80') + assert !generated.contains('"syscall \\n\\t"') + assert !generated.contains('mov %[arg4], %%r10') + assert !generated.contains('typedef long unsigned int size_t;') +} diff --git a/vlib/v/help/build/build-c.txt b/vlib/v/help/build/build-c.txt index 80048b868..ceeb78cfa 100644 --- a/vlib/v/help/build/build-c.txt +++ b/vlib/v/help/build/build-c.txt @@ -171,6 +171,9 @@ see also `v help build`. -m32, -m64 Whether 32-bit or 64-bit machine code will be generated. + On x86 C backend builds, `-m32` also selects the i386 target unless you + already passed an explicit `-arch`, so `$if i386 {}` and `*.i386.v` + files follow the same target width. NB: if you need to produce 32-bit code, *and* you are cross compiling to another OS, you may need to also set the environment variable VCROSS_COMPILER_NAME or pass `-cc your-compiler` for a one-off override, diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 2b1b10104..b1eb8a172 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -792,6 +792,10 @@ pub fn parse_args_and_show_errors(known_external_commands []string, args []strin '-m32', '-m64' { res.m64 = arg[2] == `6` res.cflags += ' ${arg}' + res.build_options << arg + if arg == '-m32' && res.arch == ._auto { + res.arch = .i386 + } } '-color' { res.use_color = .always diff --git a/vlib/v/pref/pref_test.v b/vlib/v/pref/pref_test.v index f38caed5e..217c264a3 100644 --- a/vlib/v/pref/pref_test.v +++ b/vlib/v/pref/pref_test.v @@ -146,6 +146,22 @@ fn test_musl_keeps_explicit_gc_selection() { assert prefs.gc_mode == .boehm_full_opt } +fn test_m32_sets_i386_arch_when_not_explicitly_set() { + target := os.join_path(vroot, 'examples', 'hello_world.v') + prefs, _ := pref.parse_args_and_show_errors([], ['', '-m32', target], false) + assert !prefs.m64 + assert prefs.arch == .i386 + assert prefs.build_options.contains('-m32') +} + +fn test_m32_does_not_override_explicit_arch() { + target := os.join_path(vroot, 'examples', 'hello_world.v') + prefs, _ := pref.parse_args_and_show_errors([], ['', '-arch', 'amd64', '-m32', target], false) + assert !prefs.m64 + assert prefs.arch == .amd64 + assert prefs.build_options.contains('-m32') +} + fn test_v_cmds_and_flags() { build_cmd_res := os.execute('${vexe} build ${vroot}/examples/hello_world.v') assert build_cmd_res.output.trim_space() == 'Use `v ${vroot}/examples/hello_world.v` instead.' -- 2.39.5