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

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