zig icon indicating copy to clipboard operation
zig copied to clipboard

std/c: allow overriding c imports for custom OS

Open silbinarywolf opened this issue 1 year ago • 1 comments
trafficstars

Description

I've been experimenting with getting my app running on the Wii via the DevkitPro toolchain, and I've gotten various things working using the standard library which includes reading/writing files to an SD card.

However to make it work, it required patching upstream with something like this.

How I use it

Used for Wii SDL2 application here:

  • https://github.com/silbinarywolf/zig-wii-sdk/blob/7d5088ad5906c4d17077e9894f5aea0cf100de1f/examples/sdl-app/src/main.zig#L18

Example C OS zig files:

  • https://github.com/silbinarywolf/zig-wii-sdk/tree/7d5088ad5906c4d17077e9894f5aea0cf100de1f/src/c

main.zig

pub const os = struct {
    pub const c = @import("c/os.zig");
};

c/os.zig

const std = @import("std");
const c = @import("c.zig");

/// os is the C operating system definitions, ie. c/wasi.zig
pub const os = @import("wii.zig");

pub const pthread_mutex_t = @compileError("pthread not supported by Wii library. Must compile single threaded unless we polyfill pthread functions");

// NOTE(jae): 2024-06-03
// AT is seemingly not supported for the Wii as it also doesn't support "openat"
// we polyfill "openat" ourselves.
pub const AT = struct {
    /// Special value used to indicate openat should use the current working directory
    pub const FDCWD = -2;
};

// NOTE(jae): 2024-06-03
// Copied from lib/std/os/linux.zig, section: .powerpc, .powerpcle, .powerpc64, .powerpc64le
pub const O = packed struct(u32) {
    ACCMODE: std.posix.ACCMODE = .RDONLY,
    _2: u4 = 0,
    CREAT: bool = false,
    EXCL: bool = false,
    NOCTTY: bool = false,
    TRUNC: bool = false,
    APPEND: bool = false,
    NONBLOCK: bool = false,
    DSYNC: bool = false,
    ASYNC: bool = false,
    DIRECTORY: bool = false,
    NOFOLLOW: bool = false,
    LARGEFILE: bool = false,
    DIRECT: bool = false,
    NOATIME: bool = false,
    CLOEXEC: bool = false,
    SYNC: bool = false,
    PATH: bool = false,
    TMPFILE: bool = false,
    _: u9 = 0,
};

c/wii.zig

const builtin = @import("builtin");
const std = @import("std");
const c = @import("c.zig");

pub const _errno = struct {
    extern fn __errno() *c_int;
}.__errno;

pub const PATH_MAX = 4096;

pub const mode_t = u32;
pub const time_t = i64;

pub const STDIN_FILENO = 0;
pub const STDOUT_FILENO = 1;
pub const STDERR_FILENO = 2;

const clockid_t = i32;

// ... much more things in here, etc ...

c/c.zig

pub usingnamespace @cImport({
    // OGC Library
    @cInclude("ogcsys.h");
    @cInclude("gccore.h");

    // C library
    @cInclude("stdio.h");
    @cInclude("errno.h");
    @cInclude("fcntl.h");
    @cInclude("dirent.h");
});

Seperate to this we also have a "runtime" we build to polyfill things. c/runtime.zig

const root = @import("root");
const std = @import("std");
const builtin = @import("builtin");
const c = @import("c.zig");

comptime {
    @export(openat, .{ .name = "openat", .linkage = .strong });
    @export(write, .{ .name = "__wrap_write", .linkage = .strong });
    if (builtin.os.tag == .wasi) {
        @export(wasi_errno, .{ .name = "errno", .linkage = .strong });
    }
}

/// openat is polyfilled as it doesn't have an implementation for devkitPPC
fn openat(dirfd: c_int, pathname: [*c]const u8, flags: c_int) callconv(.C) c_int {
    // TODO(jae): 2024-06-02
    // Make this actually use dirfd
    _ = dirfd; // autofix
    const file = c.open(pathname, flags);
    return file;
}

/// wrap "write" to get printf debugging in Dolphin emulator, if the "fd" is STDOUT or STDERR we call print.
/// otherwise fallback to writing to the file descriptor
fn write(fd: i32, buf: [*]const u8, count: usize) callconv(.C) isize {
    const __real_write = struct {
        extern "c" fn __real_write(fd: std.c.fd_t, buf: [*]const u8, nbyte: usize) isize;
    }.__real_write;
    if (fd == std.c.STDOUT_FILENO or fd == std.c.STDERR_FILENO) {
        // https://stackoverflow.com/a/3767300
        return @intCast(c.printf("%.*s", count, buf));
    }
    return __real_write(fd, buf, count);
}

/// wasi_errno calls the underlying Wii toolchain errno for builds targetting .wasi
fn wasi_errno() callconv(.C) *c_int {
    const _errno = struct {
        extern fn __errno() *c_int;
    }.__errno;
    return _errno();
}

silbinarywolf avatar Jun 09 '24 09:06 silbinarywolf

I can't speak to whether this direction is desirable, but in any case, it will need rebasing after #20679.

alexrp avatar Jul 21 '24 13:07 alexrp

mmm, I think a better version of this would be good in the future but for the time-being, I've opted to go with a hacky approach with my zig-wii-sdk and target .wasi, then provide those Wasi functions and call the underlying C library code.

silbinarywolf avatar Jul 22 '24 05:07 silbinarywolf