Smaller Io interface (Io-core)

According to the standard library documentation, the current Io interface contains the following:

  • file system
  • networking
  • processes
  • time and sleeping
  • randomness
  • async, await, concurrent, and cancel
  • concurrent queues
  • wait groups and select
  • mutexes, futexes, events, and conditions
  • memory mapped files

A lot of this stuff is completely unavailable in certain environments such as in embedded systems, so attempting to use the Io interface yields a lot of wasted memory, which can be very important in memory constrained environments such as embedded systems.

Perhaps it’s time for Zig to consider an smaller Io interface (Io-core) in the standard library that still supports async and concurrency, but doesn’t have stuff like filesystems which require an entire operating system.

3 Likes

I suspect what is considered as core has as many opinions as there are potential users. Good luck reaching a consensus :blush:

1 Like

Getting rid of the os-dependent filesystem stuff alone from the Io interface already gets rid of 54 function pointers, which is more than half of the function pointers in the current Io interface. That and removing whatever other os-dependent stuff can result in the Io-core. Ideally Io-core would support anything that could be done with Rust core or Rust core+alloc.

1 Like

Just wait a few releases. Restricted function pointers will essentially turn this into static dispatch, so code can be eliminated from the binary. And the cost of indirect calls is gone.

18 Likes

There are several topics where the same issue already discussed
You are not alone

1 Like

I believe the answer to that problem is

  1. Io.Operation where some operations are grouped together in one function (à la “ioctl” )

  2. Restricted function pointer which is complicated name to detect that there is only one Io implem in your program, so all Io function call are static dispatched (not sure how it removes the vtable?)

But curious which platform are you targeting ?

Zig’s goal is optimal code generation across all environments, including embedded. Solutions which split the std lib (like rust’s no_std) are sub optimal. The compiler knows where the dead code is, and it’s reasonable to expect a compiler to be able to delete it. We should be more imaginative and not be beholden to status quo of embedded being annoying AF to work on.

10 Likes

I wonder to what extent https://codeberg.org/ziglang/zig/pulls/31403 is already helping out with that

Which dead code? You can totally create your own IO vtable with undefined function pointer for all APIs you don’t use.

It’s even possible to wrap other implementation to provide more functionalities. Like I have an IO implementation that can read S3 files.

in oop it’s called god object

1 Like

It’s just an abstraction for using the most important part of the std library, IO. You could almost consider it the OS interface. Since IO in the OS can be done in two ways these days, sync or async, an abstraction is justified. It is large because IO is a big topic. I’m sure it is annoying for those who don’t need or want the ability to write code that works for both sync and sync, but solving this problem is part of Zig’s charter and has been for a long time.

3 Likes

That IO is the OS interface is a big problem for using the IO interface in environments without an OS, since the IO then contains a whole bunch of unnecessary information about OS operations such as filesystem operations, that ends up wasting memory and code. Hence this discussion about a smaller IO abstraction that doesn’t contain any code relevant to an OS.

1 Like

You’re right. My response was to a more general comment about the IO interface, so I was off-topic here.

1 Like

No, it’s fine; you weren’t the one who started the comments about general IO interfaces. The person who you replied to called the current Io interface a “God object”.

The smaller Io interface proposed here, stripped of OS relevant function pointers, would also be a “God object” in the OS-less contexts where the smaller Io interface would be relevant, and there likely will be plenty of people who would hate the smaller Io interface as well for being a “God object”.

I have read the proposal, but admit that the details are a bit over my head.

I can see how RFP eliminates the dead code from the output, but I’m not sure if the vtable itself also shrinks to fit .. or .. if going static dispatch effectively eliminates the vtable overhead altogether by inlining everything.

Can you elaborate please ?

2 Likes

The vtable can’t shrink this way, but it becomes just an array of ints, not backed by the actual implementation. The bloat comes from the functions that are included in binary because they are used in the vtable. And that is no longer needed.

RFP will essentially turn a function pointer into u32 index into the table of allowed implementations and then do something like this at the call site:

switch (index) { 0 => call_foo(), 1 => call_bar(), else => unreachable }

In the current implementation, it’s actually done differently, it delegates the optimization to LLVM and just gives it additional metadata. I’m not sure if that’s actually better than emitting a switch like this from zig’s codegen, especially for small number of implementations like Io has, but on a high-level, it’s functionally the same.

If you only have one allowed implementation, it turns the function calls into a static dispatch, no runtime checking at all. If there are multiple implementations in your binary, it still does dynamic dispatch, but because all variants are known, it can actually inline or do similar optimizations.

4 Likes

But you can already do that today? Just create one IO vtable in your program with undefined for the API doesn’t have and you’re good.

IO is the new POSIX.

It’s mind wrapping because it reverse how we think about OS. But it also amazing eg I implemented in 1 hour an IO for a closed source OS and could reuse previous code.

Yes, you can, but people don’t want to be creating Io wrappers. They just want to use it.

2 Likes

If your goal is to just use it, you can’t complain about the easy to use but not optimal in every scenario default.

Easy thing should be easy, hard thing should be possible.

1 Like

e.g. in iRMX

sync. operations were just on 4 level , 1-3 levels were completely async

Every level was separated “abstraction” (Intel called it “user interface”)

According to Intel:

iRmx has object-oriented architecture…"

Introduction to iRMX86

I can add - without god object

I am completely OK with Io as experiment