Skip to content

Commit

Permalink
more robust MSVC linking compat
Browse files Browse the repository at this point in the history
  • Loading branch information
rdunnington committed Mar 27, 2024
1 parent 097cb20 commit 0a94c1e
Showing 1 changed file with 276 additions and 25 deletions.
301 changes: 276 additions & 25 deletions src/cffi.zig
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const AllocError = std.mem.Allocator.Error;

const core = @import("core.zig");
Expand Down Expand Up @@ -106,7 +107,7 @@ const INVALID_FUNC_INDEX = std.math.maxInt(u32);
var cffi_gpa = std.heap.GeneralPurposeAllocator(.{}){};

// const CAllocator = struct {
// const AllocError = std.mem.Allocator.Error;
// const AllocError = std.mem.Allocator.Error;

// fallback: FallbackAllocator,
// alloc_func: ?CAllocFunc = null,
Expand All @@ -117,7 +118,7 @@ var cffi_gpa = std.heap.GeneralPurposeAllocator(.{}){};
// fn allocator(self: *CAllocator) std.mem.Allocator() {
// if (alloc_func != null and realloc_func != null and free_func != null) {
// return std.mem.Allocator.init(
// self,
// self,
// alloc,
// resize,
// free
Expand All @@ -128,26 +129,26 @@ var cffi_gpa = std.heap.GeneralPurposeAllocator(.{}){};
// }

// fn alloc(ptr: *anyopaque, len: usize, ptr_align: u29, len_align: u29, ret_addr: usize) AllocError![]u8 {
// _ = ret_addr;

// var allocator = @ptrCast(*CAllocator, @alignCast(@alignOf(CAllocator), ptr));
// const size =
// const mem_or_null: ?[*]anyopaque = allocator.alloc_func(size, allocator.userdata);
// if (mem_or_null) |mem| {
// var bytes = @ptrCast([*]u8, @alignCast(1, mem));
// return bytes[0..size];
// } else {
// return AllocError.OutOfMemory;
// }
// _ = ret_addr;

// var allocator = @ptrCast(*CAllocator, @alignCast(@alignOf(CAllocator), ptr));
// const size =
// const mem_or_null: ?[*]anyopaque = allocator.alloc_func(size, allocator.userdata);
// if (mem_or_null) |mem| {
// var bytes = @ptrCast([*]u8, @alignCast(1, mem));
// return bytes[0..size];
// } else {
// return AllocError.OutOfMemory;
// }
// }

// fn resize(ptr: *anyopaque, buf: []u8, buf_align: u29, new_len: usize, len_align: u29, ret_addr: usize) ?usize {

// }
// }

// fn free(ptr: *anyopaque, buf: []u8, buf_align: u29, ret_addr: usize) void {
// fn free(ptr: *anyopaque, buf: []u8, buf_align: u29, ret_addr: usize) void {

// }
// }
// };

// var cffi_allocator = CAllocator{ .fallback = FallbackAllocator{} };
Expand Down Expand Up @@ -550,15 +551,6 @@ export fn bb_func_handle_isvalid(c_handle: CFuncHandle) bool {
return c_handle.index != INVALID_FUNC_INDEX;
}

// NOTE: Zig expects this function to be present during linking, which would be fine if zig linked
// this code, but when linking with the MSVC compiler, the compiler runtime doesn't provide this
// function. Manually defining it here ensures the linker error gets resolved.
fn ___chkstk_ms() callconv(.Naked) void {}

comptime {
@export(___chkstk_ms, .{ .name = "___chkstk_ms", .linkage = .Weak });
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// Local helpers

Expand All @@ -581,3 +573,262 @@ fn translateError(err: anyerror) CError {
else => return CError.Failed,
}
}

///////////////////////////////////////////////////////////////////////////////////////////////////
// MSVC linking compat

// NOTE: Zig expects various chkstk functions to be present during linking, which would be fine if
// zig or clang linked this code, but when linking a static lib with the MSVC compiler, the compiler
// runtime has different names for these functions. Here we borrow the compiler_rt stack_probe.zig
// file and adapt it for our uses to ensure we can link with both clang and msvc runtimes.

comptime {
if (builtin.os.tag == .windows) {
const is_mingw = builtin.os.tag == .windows and builtin.abi.isGnu();

// Default stack-probe functions emitted by LLVM
if (is_mingw) {
@export(_chkstk, .{ .name = "_alloca", .linkage = .Weak });
@export(___chkstk_ms, .{ .name = "___chkstk_ms", .linkage = .Weak });

if (builtin.cpu.arch.isAARCH64()) {
@export(__chkstk, .{ .name = "__chkstk", .linkage = .Weak });
}
} else if (!builtin.link_libc) {
// This symbols are otherwise exported by MSVCRT.lib
@export(_chkstk, .{ .name = "_chkstk", .linkage = .Weak });
@export(__chkstk, .{ .name = "__chkstk", .linkage = .Weak });
}
}

switch (builtin.cpu.arch) {
.x86,
.x86_64,
=> {
@export(zig_probe_stack, .{ .name = "__zig_probe_stack", .linkage = .Weak });
},
else => {},
}
}

// Zig's own stack-probe routine (available only on x86 and x86_64)
fn zig_probe_stack() callconv(.Naked) void {
@setRuntimeSafety(false);

// Versions of the Linux kernel before 5.1 treat any access below SP as
// invalid so let's update it on the go, otherwise we'll get a segfault
// instead of triggering the stack growth.

switch (builtin.cpu.arch) {
.x86_64 => {
// %rax = probe length, %rsp = stack pointer
asm volatile (
\\ push %%rcx
\\ mov %%rax, %%rcx
\\ cmp $0x1000,%%rcx
\\ jb 2f
\\ 1:
\\ sub $0x1000,%%rsp
\\ orl $0,16(%%rsp)
\\ sub $0x1000,%%rcx
\\ cmp $0x1000,%%rcx
\\ ja 1b
\\ 2:
\\ sub %%rcx, %%rsp
\\ orl $0,16(%%rsp)
\\ add %%rax,%%rsp
\\ pop %%rcx
\\ ret
);
},
.x86 => {
// %eax = probe length, %esp = stack pointer
asm volatile (
\\ push %%ecx
\\ mov %%eax, %%ecx
\\ cmp $0x1000,%%ecx
\\ jb 2f
\\ 1:
\\ sub $0x1000,%%esp
\\ orl $0,8(%%esp)
\\ sub $0x1000,%%ecx
\\ cmp $0x1000,%%ecx
\\ ja 1b
\\ 2:
\\ sub %%ecx, %%esp
\\ orl $0,8(%%esp)
\\ add %%eax,%%esp
\\ pop %%ecx
\\ ret
);
},
else => {},
}

unreachable;
}

fn win_probe_stack_only() void {
@setRuntimeSafety(false);

switch (builtin.cpu.arch) {
.x86_64 => {
asm volatile (
\\ push %%rcx
\\ push %%rax
\\ cmp $0x1000,%%rax
\\ lea 24(%%rsp),%%rcx
\\ jb 1f
\\ 2:
\\ sub $0x1000,%%rcx
\\ test %%rcx,(%%rcx)
\\ sub $0x1000,%%rax
\\ cmp $0x1000,%%rax
\\ ja 2b
\\ 1:
\\ sub %%rax,%%rcx
\\ test %%rcx,(%%rcx)
\\ pop %%rax
\\ pop %%rcx
\\ ret
);
},
.x86 => {
asm volatile (
\\ push %%ecx
\\ push %%eax
\\ cmp $0x1000,%%eax
\\ lea 12(%%esp),%%ecx
\\ jb 1f
\\ 2:
\\ sub $0x1000,%%ecx
\\ test %%ecx,(%%ecx)
\\ sub $0x1000,%%eax
\\ cmp $0x1000,%%eax
\\ ja 2b
\\ 1:
\\ sub %%eax,%%ecx
\\ test %%ecx,(%%ecx)
\\ pop %%eax
\\ pop %%ecx
\\ ret
);
},
else => {},
}
if (comptime builtin.cpu.arch.isAARCH64()) {
// NOTE: page size hardcoded to 4096 for now
asm volatile (
\\ lsl x16, x15, #4
\\ mov x17, sp
\\1:
\\
\\ sub x17, x17, 4096
\\ subs x16, x16, 4096
\\ ldr xzr, [x17]
\\ b.gt 1b
\\
\\ ret
);
}

unreachable;
}

fn win_probe_stack_adjust_sp() void {
@setRuntimeSafety(false);

switch (builtin.cpu.arch) {
.x86_64 => {
asm volatile (
\\ push %%rcx
\\ cmp $0x1000,%%rax
\\ lea 16(%%rsp),%%rcx
\\ jb 1f
\\ 2:
\\ sub $0x1000,%%rcx
\\ test %%rcx,(%%rcx)
\\ sub $0x1000,%%rax
\\ cmp $0x1000,%%rax
\\ ja 2b
\\ 1:
\\ sub %%rax,%%rcx
\\ test %%rcx,(%%rcx)
\\
\\ lea 8(%%rsp),%%rax
\\ mov %%rcx,%%rsp
\\ mov -8(%%rax),%%rcx
\\ push (%%rax)
\\ sub %%rsp,%%rax
\\ ret
);
},
.x86 => {
asm volatile (
\\ push %%ecx
\\ cmp $0x1000,%%eax
\\ lea 8(%%esp),%%ecx
\\ jb 1f
\\ 2:
\\ sub $0x1000,%%ecx
\\ test %%ecx,(%%ecx)
\\ sub $0x1000,%%eax
\\ cmp $0x1000,%%eax
\\ ja 2b
\\ 1:
\\ sub %%eax,%%ecx
\\ test %%ecx,(%%ecx)
\\
\\ lea 4(%%esp),%%eax
\\ mov %%ecx,%%esp
\\ mov -4(%%eax),%%ecx
\\ push (%%eax)
\\ sub %%esp,%%eax
\\ ret
);
},
else => {},
}

unreachable;
}

// Windows has a multitude of stack-probing functions with similar names and
// slightly different behaviours: some behave as alloca() and update the stack
// pointer after probing the stack, other do not.
//
// Function name | Adjusts the SP? |
// | x86 | x86_64 |
// ----------------------------------------
// _chkstk (_alloca) | yes | yes |
// __chkstk | yes | no |
// __chkstk_ms | no | no |
// ___chkstk (__alloca) | yes | yes |
// ___chkstk_ms | no | no |

fn _chkstk() callconv(.Naked) void {
@setRuntimeSafety(false);
@call(.always_inline, win_probe_stack_adjust_sp, .{});
}
fn __chkstk() callconv(.Naked) void {
@setRuntimeSafety(false);
if (comptime builtin.cpu.arch.isAARCH64()) {
@call(.always_inline, win_probe_stack_only, .{});
} else switch (builtin.cpu.arch) {
.x86 => @call(.always_inline, win_probe_stack_adjust_sp, .{}),
.x86_64 => @call(.always_inline, win_probe_stack_only, .{}),
else => unreachable,
}
}
fn ___chkstk() callconv(.Naked) void {
@setRuntimeSafety(false);
@call(.always_inline, win_probe_stack_adjust_sp, .{});
}
fn __chkstk_ms() callconv(.Naked) void {
@setRuntimeSafety(false);
@call(.always_inline, win_probe_stack_only, .{});
}
fn ___chkstk_ms() callconv(.Naked) void {
@setRuntimeSafety(false);
@call(.always_inline, win_probe_stack_only, .{});
}

0 comments on commit 0a94c1e

Please sign in to comment.