Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions vlib/v/driver/driver.v
Original file line number Diff line number Diff line change
Expand Up @@ -8080,6 +8080,7 @@ const v3_cache_compiler_runtime_inputs = [
'vlib/builtin/segfault_handler_nix.c',
'vlib/builtin/segfault_handler_nix.h',
'vlib/v/driver/run_signals_nix.c',
'vlib/v/driver/native_object_cache_impl.c',
'vlib/builtin/core_windows.c',
'vlib/builtin/core_windows.h',
'vlib/time/ticks_windows.c',
Expand Down
42 changes: 1 addition & 41 deletions vlib/v/driver/native_object_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,6 @@ long syscall(long, ...);
#include <sys/syscall.h>
#endif

/* Use the syscall: Bionic's renameat2 wrapper is unavailable before API 30.
* Keep these guards in C so a portable vc snapshot can build on other hosts. */
static inline int v_native_object_cache_rename_noreplace(const char *source, const char *destination) {
#if defined(__linux__) && defined(SYS_renameat2)
/* Linux AT_FDCWD is -100 and RENAME_NOREPLACE is 1. Avoid importing fcntl
* types into headerless snapshots or requiring the flag in old headers. */
if (syscall(SYS_renameat2, -100, source, -100, destination, 1U) == 0) {
return 0;
}
return errno;
#else
(void)source;
(void)destination;
return ENOSYS;
#endif
}

static inline int v_native_object_cache_publish(const char *source, const char *destination) {
#ifndef _WIN32
#ifdef V_NATIVE_OBJECT_CACHE_HEADERLESS
if (link((char *)source, (char *)destination) == 0) {
#else
if (link(source, destination) == 0) {
#endif
return 0;
}
const int code = errno;
#if defined(__linux__)
/* Android SELinux can forbid links in app-private storage; some filesystems
* do not implement hard links. Neither prevents an atomic no-replace rename. */
if (code == EACCES || code == EPERM || code == EOPNOTSUPP) {
return v_native_object_cache_rename_noreplace(source, destination);
}
#endif
return code;
#else
(void)source;
(void)destination;
return ENOSYS;
#endif
}
#include "native_object_cache_impl.c"

#endif
84 changes: 84 additions & 0 deletions vlib/v/driver/native_object_cache_impl.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/* Shared by the SDK-backed header and standalone compiler snapshots. */
#ifndef V_NATIVE_OBJECT_CACHE_IMPL
#define V_NATIVE_OBJECT_CACHE_IMPL

#if defined(ENOSYS)
#define V_NATIVE_OBJECT_CACHE_ENOSYS ENOSYS
#elif defined(__linux__)
#define V_NATIVE_OBJECT_CACHE_ENOSYS 38
#elif defined(_WIN32)
#define V_NATIVE_OBJECT_CACHE_ENOSYS 40
#else
#define V_NATIVE_OBJECT_CACHE_ENOSYS 78
#endif

#if defined(__linux__)
#if defined(EOPNOTSUPP)
#define V_NATIVE_OBJECT_CACHE_EOPNOTSUPP EOPNOTSUPP
#else
#define V_NATIVE_OBJECT_CACHE_EOPNOTSUPP 95
#endif
/* Linux UAPI syscall tables: x86, arm, asm-generic, powerpc and s390.
* Native headers remain authoritative when the SDK provides the number. */
#if defined(SYS_renameat2)
#define V_NATIVE_OBJECT_CACHE_RENAMEAT2 SYS_renameat2
#elif defined(__x86_64__) && defined(__ILP32__)
#define V_NATIVE_OBJECT_CACHE_RENAMEAT2 (0x40000000 + 316)
#elif defined(__x86_64__)
#define V_NATIVE_OBJECT_CACHE_RENAMEAT2 316
#elif defined(__i386__)
#define V_NATIVE_OBJECT_CACHE_RENAMEAT2 353
#elif defined(__arm__)
#define V_NATIVE_OBJECT_CACHE_RENAMEAT2 382
#elif defined(__aarch64__) || defined(__riscv) || defined(__loongarch__)
#define V_NATIVE_OBJECT_CACHE_RENAMEAT2 276
#elif defined(__powerpc__)
#define V_NATIVE_OBJECT_CACHE_RENAMEAT2 357
#elif defined(__s390__)
#define V_NATIVE_OBJECT_CACHE_RENAMEAT2 347
#endif
#endif

/* Use the syscall: Bionic's renameat2 wrapper is unavailable before API 30.
* Keep these guards in C so a portable vc snapshot can build on other hosts. */
static inline int v_native_object_cache_rename_noreplace(const char *source, const char *destination) {
#if defined(__linux__) && defined(V_NATIVE_OBJECT_CACHE_RENAMEAT2)
/* Linux AT_FDCWD is -100 and RENAME_NOREPLACE is 1. Avoid importing fcntl
* types into headerless snapshots or requiring the flag in old headers. */
if (syscall(V_NATIVE_OBJECT_CACHE_RENAMEAT2, -100, source, -100, destination, 1U) == 0) {
return 0;
}
return errno;
#else
(void)source;
(void)destination;
return V_NATIVE_OBJECT_CACHE_ENOSYS;
#endif
}

static inline int v_native_object_cache_publish(const char *source, const char *destination) {
#ifndef _WIN32
#ifdef V_NATIVE_OBJECT_CACHE_HEADERLESS
if (link((char *)source, (char *)destination) == 0) {
#else
if (link(source, destination) == 0) {
#endif
return 0;
}
const int code = errno;
#if defined(__linux__)
/* Android SELinux can forbid links in app-private storage; some filesystems
* do not implement hard links. Neither prevents an atomic no-replace rename. */
if (code == EACCES || code == EPERM || code == V_NATIVE_OBJECT_CACHE_EOPNOTSUPP) {
return v_native_object_cache_rename_noreplace(source, destination);
}
#endif
return code;
#else
(void)source;
(void)destination;
return V_NATIVE_OBJECT_CACHE_ENOSYS;
#endif
}

#endif
30 changes: 28 additions & 2 deletions vlib/v/gen/c/bootstrap_runtime.v
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
module c

// headerless_native_object_cache_helper supplies the shipped publication helper
// without importing SDK headers into a standalone compiler translation unit.
fn (mut g FlatGen) headerless_native_object_cache_helper() {
root := g.compiler_vroot.replace('\\', '/').trim_right('/')
if !g.core_header_directives.any(normalized_c_include_arg_path(it.directive.text.all_after('#include').trim_space()) == root + '/vlib/v/driver/native_object_cache.h') {
return
}
g.writeln('#define V_NATIVE_OBJECT_CACHE_HEADERLESS 1')
g.writeln('#ifndef _WIN32')
g.writeln(g.cpp_c_linkage('int link(char*, char*);'))
g.writeln('#if defined(__linux__)')
g.writeln(g.cpp_c_linkage('long syscall(long, ...);'))
g.writeln('#endif')
g.writeln('#endif')
g.write($embed_file('@VEXEROOT/vlib/v/driver/native_object_cache_impl.c').to_string())

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Fingerprint the newly embedded native-cache implementation

This new $embed_file target is absent from v3_cache_compiler_runtime_inputs in driver.v. That list contributes the running backend's external inputs to the cache salt for development/older-bootstrap consumers that read embedded files at runtime; changing this implementation would leave that part of the cache identity unchanged. Please add vlib/v/driver/native_object_cache_impl.c to the list alongside the other embedded runtime inputs. The existing focused test test_module_cache_compiler_runtime_inputs_cover_compiler_embedded_files reproducibly fails at cache_prune_test.v:206 on this exact head and names this new embed. Modern compiler self-builds that embed the bytes are separately covered by executable identity, but that does not cover the runtime-reading cases.

g.collect_preserved_c_fns(['v_native_object_cache_publish', 'v_native_object_cache_rename_noreplace',
'link', 'syscall'])
}

// portable_windows_runtime_helpers embeds SDK-backed helpers without checkout-local objects.
fn (mut g FlatGen) portable_windows_runtime_helpers() {
g.writeln('#ifdef _WIN32')
Expand Down Expand Up @@ -134,14 +153,21 @@ static inline int v_sync_thread_create_detached(void* start, void* arg) {
}
'

// headerless_cmdexec_poll_helper replaces only the shipped poll header when the
// headerless_cmdexec_poll_helper replaces the shipped poll headers when the
// standalone ABI owns libc declarations; SDK-backed units keep the native helper.
fn (mut g FlatGen) headerless_cmdexec_poll_helper() {
if g.target.os == 'windows' || !g.core_header_directives.any(it.directive.module == 'cmdexec') {
os_poll := g.core_header_directives.any(it.directive.module == 'os'
&& normalized_c_include_arg_path(it.directive.text.all_after('#include').trim_space()) == g.compiler_vroot.replace('\\', '/').trim_right('/') + '/vlib/os/fd_poll.h')
if g.target.os == 'windows'
|| (!os_poll && !g.core_header_directives.any(it.directive.module == 'cmdexec')) {
return
}
g.write(headerless_cmdexec_poll)
g.collect_preserved_c_fns(['v_cmdexec_poll_revents'])
if os_poll {
g.writeln('static inline short v_os_fd_poll_revents(int fd, short events) { return v_cmdexec_poll_revents(fd, events); }')
g.collect_preserved_c_fns(['v_os_fd_poll_revents'])
}
}

const headerless_cmdexec_poll = r'
Expand Down
16 changes: 14 additions & 2 deletions vlib/v/gen/c/cleanc.v
Original file line number Diff line number Diff line change
Expand Up @@ -6763,7 +6763,12 @@ fn (g &FlatGen) core_native_source_header_is_redeclared(root string, source stri
|| normalized_c_include_arg_path(include_arg) == root + '/vlib/builtin/core_windows.h'
}
'builtin/closure/closure_windows.c.v' { return include_arg == '<synchapi.h>' }
'os/fd.c.v' { return include_arg in ['<winsock2.h>', '<poll.h>'] }
'os/fd.c.v' {
return include_arg in ['<winsock2.h>', '<poll.h>']
|| (g.target.os in ['macos', 'linux', 'android', 'termux', 'freebsd', 'openbsd',
'dragonfly', 'ios']
&& normalized_c_include_arg_path(include_arg) == root + '/vlib/os/fd_poll.h')
}
'os/mmap_nix.c.v' {
return g.target.pointer_bits == 64 && g.target.os in ['macos', 'linux', 'android',
'termux', 'freebsd', 'openbsd', 'dragonfly']
Expand All @@ -6775,6 +6780,9 @@ fn (g &FlatGen) core_native_source_header_is_redeclared(root string, source stri
'v/driver/replace_file_windows.c.v' {
return include_arg == '<windows.h>'
}
'v/driver/native_object_cache.c.v' {
return normalized_c_include_arg_path(include_arg) == root + '/vlib/v/driver/native_object_cache.h'
}
'v/cmdexec/cmdexec_pipe.c.v' {
return include_arg == '<windows.h>'
|| (g.target.os in ['macos', 'linux', 'android', 'termux', 'freebsd', 'openbsd',
Expand Down Expand Up @@ -7127,7 +7135,10 @@ fn (mut g FlatGen) collect_c_directive_at(node_idx int, module_name string, node
}
}
include_dirs := c_flag_include_dirs(g.c_flags)
if node.value == 'include' && g.core_native_header_is_redeclared(source_file, include_arg) {
native_cache_insert := node.value == 'insert'
&& normalized_c_include_arg_path(include_arg) == g.compiler_vroot.replace('\\', '/').trim_right('/') + '/vlib/v/driver/native_object_cache.h'
if (node.value == 'include' || native_cache_insert)
&& g.core_native_header_is_redeclared(source_file, include_arg) {
// Native objects still compile against their SDK; only the generated V
// translation unit uses the bodyless declarations and ABI preamble.
g.add_native_source_context_directive(module_name, c_native_source_context_header_include(include_arg, g.compiler_vroot, source_file, include_dirs), before_import)
Expand Down Expand Up @@ -20716,6 +20727,7 @@ fn (mut g FlatGen) preamble() {
} else {
g.headerless_libc_preamble()
g.headerless_cmdexec_poll_helper()
g.headerless_native_object_cache_helper()
if g.headerless_compiler_bootstrap() {
g.portable_native_runtime_helpers()
}
Expand Down
40 changes: 40 additions & 0 deletions vlib/v/gen/c/cpp_compat_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -291,3 +291,43 @@ int main() {
assert ran.exit_code == 0, ran.output
}
}

fn test_cpp_headerless_native_cache_libc_linkage() {
$if windows {
return
}
cpp := cpp_test_compiler() or { return }
root := os.join_path(os.vtmp_dir(), 'cpp_native_cache_${os.getpid()}')
os.mkdir_all(root)!
defer { os.rmdir_all(root) or {} }
mut g := cpp_test_gen()
g.compiler_vroot = @VEXEROOT
g.core_header_directives << DeferredCoreHeader{
source_file: os.join_path(@VEXEROOT, 'vlib/v/driver/native_object_cache.c.v')
directive: CDirective{ module: 'driver', text: '#include "' + @VEXEROOT + '/vlib/v/driver/native_object_cache.h"' }
}
g.headerless_native_object_cache_helper()
source := os.join_path(root, 'main.cpp')
os.write_file(source, '#include <errno.h>
${g.sb.str()}
// Match the later gettid compatibility declaration in a compiler translation unit.
#ifdef __linux__
extern "C" { long syscall(long, ...); }
#endif
int main(int argc, char** argv) {
if (argc != 3) return 1;
if (v_native_object_cache_publish(argv[1], argv[2]) != ENOENT) return 2;
#ifdef __linux__
if (v_native_object_cache_rename_noreplace(argv[1], argv[2]) != ENOENT) return 3;
#endif
return 0;
}
')!
for standard in cpp_test_standards() {
binary := os.join_path(root, 'program')
built := cmdexec.run(cpp, ['-std=${standard}', source, '-o', binary])
assert built.exit_code == 0, built.output
ran := cmdexec.run(binary, [os.join_path(root, 'missing'), os.join_path(root, 'destination')])
assert ran.exit_code == 0, ran.output
}
}
6 changes: 6 additions & 0 deletions vlib/v/gen/c/mmap_headerless_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,16 @@ fn test_mmap_header_ownership_holds_for_a_cached_header_of_the_whole_module() {
]
assert g.core_native_header_is_redeclared('/cache/cmdexec.vh', poll_h)
assert !g.core_native_header_is_redeclared('/cache/cmdexec.vh', '<poll.h>')
os_poll_h := '"/vroot/vlib/os/fd_poll.h"'
a.cached_header_module_sources['/cache/os.vh'] << '/vroot/vlib/os/fd.c.v'
assert g.core_native_header_is_redeclared('/cache/os.vh', os_poll_h)
assert !g.core_native_header_is_redeclared('/cache/other.vh', os_poll_h)
assert !g.core_native_header_is_redeclared('/project/os/fd.c.v', os_poll_h)
// The SDK modes keep every include of a cached header too.
g.no_builtin = true
assert !g.core_native_header_is_redeclared('/cache/os.vh', '<sys/mman.h>')
assert !g.core_native_header_is_redeclared('/cache/cmdexec.vh', poll_h)
assert !g.core_native_header_is_redeclared('/cache/os.vh', os_poll_h)
g.no_builtin = false
g.output_cross_c = true
assert !g.core_native_header_is_redeclared('/cache/os.vh', '<sys/mman.h>')
Expand Down