zig/lib/c.zig
Jacob Young 228c956377 std: finish cleanup up asm
This also required implementing the necessary syntax in the x86_64 backend.
2023-07-31 03:49:21 -04:00

555 lines
18 KiB
Zig

//! This is Zig's multi-target implementation of libc.
//! When builtin.link_libc is true, we need to export all the functions and
//! provide an entire C API.
const std = @import("std");
const builtin = @import("builtin");
const math = std.math;
const isNan = std.math.isNan;
const maxInt = std.math.maxInt;
const native_os = builtin.os.tag;
const native_arch = builtin.cpu.arch;
const native_abi = builtin.abi;
const is_wasm = switch (native_arch) {
.wasm32, .wasm64 => true,
else => false,
};
const is_msvc = switch (native_abi) {
.msvc => true,
else => false,
};
const is_freestanding = switch (native_os) {
.freestanding => true,
else => false,
};
comptime {
if (is_freestanding and is_wasm and builtin.link_libc) {
@export(wasm_start, .{ .name = "_start", .linkage = .Strong });
}
if (native_os == .linux) {
@export(clone, .{ .name = "clone" });
}
if (builtin.link_libc) {
@export(strcmp, .{ .name = "strcmp", .linkage = .Strong });
@export(strncmp, .{ .name = "strncmp", .linkage = .Strong });
@export(strerror, .{ .name = "strerror", .linkage = .Strong });
@export(strlen, .{ .name = "strlen", .linkage = .Strong });
@export(strcpy, .{ .name = "strcpy", .linkage = .Strong });
@export(strncpy, .{ .name = "strncpy", .linkage = .Strong });
@export(strcat, .{ .name = "strcat", .linkage = .Strong });
@export(strncat, .{ .name = "strncat", .linkage = .Strong });
} else if (is_msvc) {
@export(_fltused, .{ .name = "_fltused", .linkage = .Strong });
}
}
// Avoid dragging in the runtime safety mechanisms into this .o file,
// unless we're trying to test this file.
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
@setCold(true);
_ = error_return_trace;
if (builtin.is_test) {
std.debug.panic("{s}", .{msg});
}
switch (native_os) {
.freestanding, .other, .amdhsa, .amdpal => while (true) {},
else => std.os.abort(),
}
}
extern fn main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
fn wasm_start() callconv(.C) void {
_ = main(0, undefined);
}
var _fltused: c_int = 1;
fn strcpy(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 {
var i: usize = 0;
while (src[i] != 0) : (i += 1) {
dest[i] = src[i];
}
dest[i] = 0;
return dest;
}
test "strcpy" {
var s1: [9:0]u8 = undefined;
s1[0] = 0;
_ = strcpy(&s1, "foobarbaz");
try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
}
fn strncpy(dest: [*:0]u8, src: [*:0]const u8, n: usize) callconv(.C) [*:0]u8 {
var i: usize = 0;
while (i < n and src[i] != 0) : (i += 1) {
dest[i] = src[i];
}
while (i < n) : (i += 1) {
dest[i] = 0;
}
return dest;
}
test "strncpy" {
var s1: [9:0]u8 = undefined;
s1[0] = 0;
_ = strncpy(&s1, "foobarbaz", @sizeOf(@TypeOf(s1)));
try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
}
fn strcat(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 {
var dest_end: usize = 0;
while (dest[dest_end] != 0) : (dest_end += 1) {}
var i: usize = 0;
while (src[i] != 0) : (i += 1) {
dest[dest_end + i] = src[i];
}
dest[dest_end + i] = 0;
return dest;
}
test "strcat" {
var s1: [9:0]u8 = undefined;
s1[0] = 0;
_ = strcat(&s1, "foo");
_ = strcat(&s1, "bar");
_ = strcat(&s1, "baz");
try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
}
fn strncat(dest: [*:0]u8, src: [*:0]const u8, avail: usize) callconv(.C) [*:0]u8 {
var dest_end: usize = 0;
while (dest[dest_end] != 0) : (dest_end += 1) {}
var i: usize = 0;
while (i < avail and src[i] != 0) : (i += 1) {
dest[dest_end + i] = src[i];
}
dest[dest_end + i] = 0;
return dest;
}
test "strncat" {
var s1: [9:0]u8 = undefined;
s1[0] = 0;
_ = strncat(&s1, "foo1111", 3);
_ = strncat(&s1, "bar1111", 3);
_ = strncat(&s1, "baz1111", 3);
try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
}
fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.C) c_int {
return switch (std.mem.orderZ(u8, s1, s2)) {
.lt => -1,
.eq => 0,
.gt => 1,
};
}
fn strlen(s: [*:0]const u8) callconv(.C) usize {
return std.mem.len(s);
}
fn strncmp(_l: [*:0]const u8, _r: [*:0]const u8, _n: usize) callconv(.C) c_int {
if (_n == 0) return 0;
var l = _l;
var r = _r;
var n = _n - 1;
while (l[0] != 0 and r[0] != 0 and n != 0 and l[0] == r[0]) {
l += 1;
r += 1;
n -= 1;
}
return @as(c_int, l[0]) - @as(c_int, r[0]);
}
fn strerror(errnum: c_int) callconv(.C) [*:0]const u8 {
_ = errnum;
return "TODO strerror implementation";
}
test "strncmp" {
try std.testing.expect(strncmp("a", "b", 1) < 0);
try std.testing.expect(strncmp("a", "c", 1) < 0);
try std.testing.expect(strncmp("b", "a", 1) > 0);
try std.testing.expect(strncmp("\xff", "\x02", 1) > 0);
}
// TODO we should be able to put this directly in std/linux/x86_64.zig but
// it causes a segfault in release mode. this is a workaround of calling it
// across .o file boundaries. fix comptime @ptrCast of nakedcc functions.
fn clone() callconv(.Naked) void {
switch (native_arch) {
.x86 => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// +8, +12, +16, +20, +24, +28, +32
// syscall(SYS_clone, flags, stack, ptid, tls, ctid)
// eax, ebx, ecx, edx, esi, edi
asm volatile (
\\ pushl %%ebp
\\ movl %%esp,%%ebp
\\ pushl %%ebx
\\ pushl %%esi
\\ pushl %%edi
\\ // Setup the arguments
\\ movl 16(%%ebp),%%ebx
\\ movl 12(%%ebp),%%ecx
\\ andl $-16,%%ecx
\\ subl $20,%%ecx
\\ movl 20(%%ebp),%%eax
\\ movl %%eax,4(%%ecx)
\\ movl 8(%%ebp),%%eax
\\ movl %%eax,0(%%ecx)
\\ movl 24(%%ebp),%%edx
\\ movl 28(%%ebp),%%esi
\\ movl 32(%%ebp),%%edi
\\ movl $120,%%eax
\\ int $128
\\ testl %%eax,%%eax
\\ jnz 1f
\\ popl %%eax
\\ xorl %%ebp,%%ebp
\\ calll *%%eax
\\ movl %%eax,%%ebx
\\ movl $1,%%eax
\\ int $128
\\1:
\\ popl %%edi
\\ popl %%esi
\\ popl %%ebx
\\ popl %%ebp
\\ retl
);
},
.x86_64 => {
asm volatile (
\\ movl $56,%%eax // SYS_clone
\\ movq %%rdi,%%r11
\\ movq %%rdx,%%rdi
\\ movq %%r8,%%rdx
\\ movq %%r9,%%r8
\\ movq 8(%%rsp),%%r10
\\ movq %%r11,%%r9
\\ andq $-16,%%rsi
\\ subq $8,%%rsi
\\ movq %%rcx,(%%rsi)
\\ syscall
\\ testq %%rax,%%rax
\\ jnz 1f
\\ xorl %%ebp,%%ebp
\\ popq %%rdi
\\ callq *%%r9
\\ movl %%eax,%%edi
\\ movl $60,%%eax // SYS_exit
\\ syscall
\\1: ret
\\
);
},
.aarch64, .aarch64_be => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// x0, x1, w2, x3, x4, x5, x6
// syscall(SYS_clone, flags, stack, ptid, tls, ctid)
// x8, x0, x1, x2, x3, x4
asm volatile (
\\ // align stack and save func,arg
\\ and x1,x1,#-16
\\ stp x0,x3,[x1,#-16]!
\\
\\ // syscall
\\ uxtw x0,w2
\\ mov x2,x4
\\ mov x3,x5
\\ mov x4,x6
\\ mov x8,#220 // SYS_clone
\\ svc #0
\\
\\ cbz x0,1f
\\ // parent
\\ ret
\\ // child
\\1: ldp x1,x0,[sp],#16
\\ blr x1
\\ mov x8,#93 // SYS_exit
\\ svc #0
);
},
.arm, .thumb => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// r0, r1, r2, r3, +0, +4, +8
// syscall(SYS_clone, flags, stack, ptid, tls, ctid)
// r7 r0, r1, r2, r3, r4
asm volatile (
\\ stmfd sp!,{r4,r5,r6,r7}
\\ mov r7,#120
\\ mov r6,r3
\\ mov r5,r0
\\ mov r0,r2
\\ and r1,r1,#-16
\\ ldr r2,[sp,#16]
\\ ldr r3,[sp,#20]
\\ ldr r4,[sp,#24]
\\ svc 0
\\ tst r0,r0
\\ beq 1f
\\ ldmfd sp!,{r4,r5,r6,r7}
\\ bx lr
\\
\\1: mov r0,r6
\\ bl 3f
\\2: mov r7,#1
\\ svc 0
\\ b 2b
\\3: bx r5
);
},
.riscv64 => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// a0, a1, a2, a3, a4, a5, a6
// syscall(SYS_clone, flags, stack, ptid, tls, ctid)
// a7 a0, a1, a2, a3, a4
asm volatile (
\\ # Save func and arg to stack
\\ addi a1, a1, -16
\\ sd a0, 0(a1)
\\ sd a3, 8(a1)
\\
\\ # Call SYS_clone
\\ mv a0, a2
\\ mv a2, a4
\\ mv a3, a5
\\ mv a4, a6
\\ li a7, 220 # SYS_clone
\\ ecall
\\
\\ beqz a0, 1f
\\ # Parent
\\ ret
\\
\\ # Child
\\1: ld a1, 0(sp)
\\ ld a0, 8(sp)
\\ jalr a1
\\
\\ # Exit
\\ li a7, 93 # SYS_exit
\\ ecall
);
},
.mips, .mipsel, .mips64, .mips64el => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// 3, 4, 5, 6, 7, 8, 9
// syscall(SYS_clone, flags, stack, ptid, tls, ctid)
// 2 4, 5, 6, 7, 8
asm volatile (
\\ # Save function pointer and argument pointer on new thread stack
\\ and $5, $5, -8
\\ subu $5, $5, 16
\\ sw $4, 0($5)
\\ sw $7, 4($5)
\\ # Shuffle (fn,sp,fl,arg,ptid,tls,ctid) to (fl,sp,ptid,tls,ctid)
\\ move $4, $6
\\ lw $6, 16($sp)
\\ lw $7, 20($sp)
\\ lw $9, 24($sp)
\\ subu $sp, $sp, 16
\\ sw $9, 16($sp)
\\ li $2, 4120
\\ syscall
\\ beq $7, $0, 1f
\\ nop
\\ addu $sp, $sp, 16
\\ jr $ra
\\ subu $2, $0, $2
\\1:
\\ beq $2, $0, 1f
\\ nop
\\ addu $sp, $sp, 16
\\ jr $ra
\\ nop
\\1:
\\ lw $25, 0($sp)
\\ lw $4, 4($sp)
\\ jalr $25
\\ nop
\\ move $4, $2
\\ li $2, 4001
\\ syscall
);
},
.powerpc, .powerpcle => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// 3, 4, 5, 6, 7, 8, 9
// syscall(SYS_clone, flags, stack, ptid, tls, ctid)
// 0 3, 4, 5, 6, 7
asm volatile (
\\ # store non-volatile regs r30, r31 on stack in order to put our
\\ # start func and its arg there
\\ stwu 30, -16(1)
\\ stw 31, 4(1)
\\
\\ # save r3 (func) into r30, and r6(arg) into r31
\\ mr 30, 3
\\ mr 31, 6
\\
\\ # create initial stack frame for new thread
\\ clrrwi 4, 4, 4
\\ li 0, 0
\\ stwu 0, -16(4)
\\
\\ #move c into first arg
\\ mr 3, 5
\\ #mr 4, 4
\\ mr 5, 7
\\ mr 6, 8
\\ mr 7, 9
\\
\\ # move syscall number into r0
\\ li 0, 120
\\
\\ sc
\\
\\ # check for syscall error
\\ bns+ 1f # jump to label 1 if no summary overflow.
\\ #else
\\ neg 3, 3 #negate the result (errno)
\\ 1:
\\ # compare sc result with 0
\\ cmpwi cr7, 3, 0
\\
\\ # if not 0, jump to end
\\ bne cr7, 2f
\\
\\ #else: we're the child
\\ #call funcptr: move arg (d) into r3
\\ mr 3, 31
\\ #move r30 (funcptr) into CTR reg
\\ mtctr 30
\\ # call CTR reg
\\ bctrl
\\ # mov SYS_exit into r0 (the exit param is already in r3)
\\ li 0, 1
\\ sc
\\
\\ 2:
\\
\\ # restore stack
\\ lwz 30, 0(1)
\\ lwz 31, 4(1)
\\ addi 1, 1, 16
\\
\\ blr
);
},
.powerpc64, .powerpc64le => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// 3, 4, 5, 6, 7, 8, 9
// syscall(SYS_clone, flags, stack, ptid, tls, ctid)
// 0 3, 4, 5, 6, 7
asm volatile (
\\ # create initial stack frame for new thread
\\ clrrdi 4, 4, 4
\\ li 0, 0
\\ stdu 0,-32(4)
\\
\\ # save fn and arg to child stack
\\ std 3, 8(4)
\\ std 6, 16(4)
\\
\\ # shuffle args into correct registers and call SYS_clone
\\ mr 3, 5
\\ #mr 4, 4
\\ mr 5, 7
\\ mr 6, 8
\\ mr 7, 9
\\ li 0, 120 # SYS_clone = 120
\\ sc
\\
\\ # if error, negate return (errno)
\\ bns+ 1f
\\ neg 3, 3
\\
\\1:
\\ # if we're the parent, return
\\ cmpwi cr7, 3, 0
\\ bnelr cr7
\\
\\ # we're the child. call fn(arg)
\\ ld 3, 16(1)
\\ ld 12, 8(1)
\\ mtctr 12
\\ bctrl
\\
\\ # call SYS_exit. exit code is already in r3 from fn return value
\\ li 0, 1 # SYS_exit = 1
\\ sc
);
},
.sparc64 => {
// __clone(func, stack, flags, arg, ptid, tls, ctid)
// i0, i1, i2, i3, i4, i5, sp
// syscall(SYS_clone, flags, stack, ptid, tls, ctid)
// g1 o0, o1, o2, o3, o4
asm volatile (
\\ save %%sp, -192, %%sp
\\ # Save the func pointer and the arg pointer
\\ mov %%i0, %%g2
\\ mov %%i3, %%g3
\\ # Shuffle the arguments
\\ mov 217, %%g1
\\ mov %%i2, %%o0
\\ # Add some extra space for the initial frame
\\ sub %%i1, 176 + 2047, %%o1
\\ mov %%i4, %%o2
\\ mov %%i5, %%o3
\\ ldx [%%fp + 0x8af], %%o4
\\ t 0x6d
\\ bcs,pn %%xcc, 2f
\\ nop
\\ # The child pid is returned in o0 while o1 tells if this
\\ # process is # the child (=1) or the parent (=0).
\\ brnz %%o1, 1f
\\ nop
\\ # Parent process, return the child pid
\\ mov %%o0, %%i0
\\ ret
\\ restore
\\1:
\\ # Child process, call func(arg)
\\ mov %%g0, %%fp
\\ call %%g2
\\ mov %%g3, %%o0
\\ # Exit
\\ mov 1, %%g1
\\ t 0x6d
\\2:
\\ # The syscall failed
\\ sub %%g0, %%o0, %%i0
\\ ret
\\ restore
);
},
else => @compileError("Implement clone() for this arch."),
}
}