Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Does zig reimplement some part of libc directly in zig? #7

Open
lobre opened this issue Nov 11, 2023 · 6 comments
Open

Does zig reimplement some part of libc directly in zig? #7

lobre opened this issue Nov 11, 2023 · 6 comments

Comments

@lobre
Copy link

lobre commented Nov 11, 2023

I am trying to understand the relationship with zig and libc. I get that for example it has different behaviors according to libc implementations. With glibc, it will generate the headers and dynamically link glibc. For musl, it embeds the implementation directly so it can generate static builds.

I have a few subquestions, however.

  1. How to import a libc function in my zig code, and how to build it so that it uses glibc or musl?
  2. Will zig try to reimplement libc in zig totally at some point? I see community efforts for this, but can it be something that is directly integrated in zig core at some point?
  3. What is the role of lib/std/os/linux.zig? I see that it has the implementation for functions such as tcgetattr, which is normally defined in <termios.h>. Does zig already reimplement some parts of libc directly in zig then?

To give more context for this last one, I am trying to use termios for an application that would have total control over the terminal. To me termios is part of libc. In my zig code, I see that I can use std.os.termios. It seems to be zig code however, and when I compile it without any special flags, it generates a static build. So I don't understand why I can use termios without involving any libc.

And a complementary question: if it happens that this bit of libc has been reimplemented in zig, why? And for learning purposes, can I force to use termios from libc instead somehow?

Thanks!

@expikr
Copy link

expikr commented Nov 11, 2023

Append -lc to your commands

e.g.

const std = @import("std");
pub fn main() !void {
    const arr = try std.heap.c_allocator.alloc(u8,1024);
    std.debug.print("{}",.{arr.len});
}
> zig run test.zig -lc
1024
> zig run main.zig
D:\zig-windows-x86_64-0.11.0\lib\std\heap.zig:38:13: error: C allocator is only available when linking against libc
            @compileError("C allocator is only available when linking against libc");
            ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
D:\zig-windows-x86_64-0.11.0\lib\std\c.zig:244:12: error: dependency on libc must be explicitly specified in the build command
pub extern "c" fn malloc(usize) ?*anyopaque;
           ^~~
D:\zig-windows-x86_64-0.11.0\lib\std\c\windows.zig:8:12: error: dependency on libc must be explicitly specified in the build command
pub extern "c" fn _msize(memblock: ?*anyopaque) usize;
           ^~~
D:\zig-windows-x86_64-0.11.0\lib\std\c.zig:246:12: error: dependency on libc must be explicitly specified in the build command
pub extern "c" fn free(?*anyopaque) void;
           ^~~

@thechampagne
Copy link

  1. How to import a libc function in my zig code, and how to build it so that it uses glibc or musl?
extern fn puts([*:0]const u8) c_int;

pub fn main() void {
    _ = puts("Hello World!");
}

zig build-exe main.zig -lc -target <cpu>-<os>-gnu for glibc
zig build-exe main.zig -lc -target <cpu>-<os>-musl for musl

@TUSF
Copy link

TUSF commented Nov 11, 2023

How to import a libc function in my zig code, and how to build it so that it uses glibc or musl?

As other's already said, if you're using the zig build-* subcommands, just use -lc to link libc. If you're writing a build.zig file, the command to use would be exe.linkLibC(); where exe is the output of one of the Compile steps (ie, b.addExecutable())

Will zig try to reimplement libc in zig totally at some point? I see community efforts for this, but can it be something that is directly integrated in zig core at some point?

As you mentioned, there is a community effort to implement libc completely in Zig, with the idea being to integrate it into Zig itself so that it generates the appropriate version depending on which platform you compile to. Although I'm not really sure what the state of that is, or if they'll really end up shipping it with Zig.

What is the role of lib/std/os/linux.zig? I see that it has the implementation for functions such as tcgetattr, which is normally defined in <termios.h>. Does zig already reimplement some parts of libc directly in zig then?

Linux is one of the only operating systems where syscalls are guaranteed to be stable between versions. As such, when compiling to Linux, Zig will take every opportunity to avoid going through libc, and invokes the syscalls directly. On platforms where libc is the only "standard" way to interact with the OS, Zig will fallback to using libc.

@lobre
Copy link
Author

lobre commented Nov 13, 2023

Thank you for your advice. I know that I should explicitly add -lc to link against libc (musl or glibc according to target).

After re-reading my question, I realize the most important bit is about std/os/linux.zig. This is where my struggle comes from.

Linux is one of the only operating systems where syscalls are guaranteed to be stable between versions. As such, when compiling to Linux, Zig will take every opportunity to avoid going through libc, and invokes the syscalls directly. On platforms where libc is the only "standard" way to interact with the OS, Zig will fallback to using libc.

This is interesting and helpful. I also read the following statement by Andrew in a zig issue.

std.os.linux is intended to map to raw Linux syscalls.

So I guess my question is more about knowing what is the exact purpose of std/os/linux.zig and what is its relationship with libc?

It seems there are answers in std/os.zig, directly in the header comment:

//! This file contains thin wrappers around OS-specific APIs, with these
//! specific goals in mind:
//! ...
//! * When there exists a corresponding libc function and linking libc, the libc
//!   implementation is used. Exceptions are made for known buggy areas of libc.
//!   On Linux libc can be side-stepped by using `std.os.linux` directly.
//! ...

I also see that termios is brought in scope with system.termios:

pub const termios = system.termios;

And system is set to std/os/linux.zig when the os is linux:

pub const linux = @import("os/linux.zig");

...

/// Applications can override the `system` API layer in their root source file.
/// Otherwise, when linking libc, this is the C API.
/// When not linking libc, it is the OS-specific system interface.
pub const system = if (@hasDecl(root, "os") and root.os != @This())
    root.os.system
else if (builtin.link_libc or is_windows)
    std.c
else switch (builtin.os.tag) {
    .linux => linux,
    // ...
    else => struct {},
};

In the above code, the branch std.c from the condition builtin.link_libc is before the one that sets os/linux.zig. Does it mean when libc is linked, libc is used and os/linux.zig is not used at all?

And with this context and to refine my initial question, can I consider that in libc, only the functions that just use syscalls are reimplemented in zig in os/linux.zig?

If so and when libc is not linked, that would mean that std.os.termios would not necessarily represent the full termios library, but maybe just the parts that wrap syscalls?

Thanks a lot. The relationship between zig and libc is not that trivial from what I see. And by reading open issues, it seems the organisation of all of this is still not fully frozen.

@TUSF
Copy link

TUSF commented Nov 13, 2023

Does it mean when libc is linked, libc is used and os/linux.zig is not used at all?

If you take a look at the standard library in the repo you can see that in c/linux.zig the constants defined in os/linux.zig are still used. But otherwise, yes, os/linux.zig is not used if you have libc linked.

only the functions that just use syscalls are reimplemented in zig in os/linux.zig?

As I understand it, yeah. Scanning through os/linux.zig, that seems to be the case—most of the functions present either call one of the syscall helpers, or call another function that does. But for the most part, everything under os/ isn't meant to be used directly, and exists as a backend to the std.os API. The same is true for std.c.

that would mean that std.os.termios would not necessarily represent the full termios library

std.os.termios is only the struct definition of termios, and not equivalent to the <termios.h> C API. Those functions seem to be present in os/linux.zig, and yes, seems like only the ones that require syscalls.

@nektro
Copy link
Owner

nektro commented Nov 13, 2023

lots of great info here, ziglang/zig#2879 is also relevant

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants