Cosmopolitan libc

Has anyone tried to compile Crystal with cosmopolitan libc for a portable binary?

If not, how hard is it generally to make Crystal work with an alternative libc?

2 Likes

I’m not aware of anyone trying that.

Effort depends on how much the interface differs from the existing C standard libraries. But it’s likely not that much for the majority of bindings. So you could copy and paste a lot. I’d expect to get probably > 90% working pretty quickly. Figuring out all the details in edge cases can be time consuming, though.

1 Like

Previous mention on the issue tracker: Can crystal lang implement the APE binary format? · Issue #12753 · crystal-lang/crystal · GitHub

I might give this a go after the Battlesnake spring league (a few months). My free time is pretty booked until then.

I just tried the first step: compile a hello world with an empty prelude (i.e. no stdlib). There are zero reasons that it wouldn’t work.

class String
  def to_unsafe : UInt8*
    pointerof(@c)
  end
end

lib LibC
  fun puts(UInt8*) : Int32
end

LibC.puts("hello world")

Thanks to --cross-compile and --target we can build a couple object files for the x86_64 and aarch64 architectures. I used x86_64-linux-gnu and aarch64-linux-gnu as pseudo targets.

I ran cosmocc -v hello.c to figure out the underlying calls to gcc and apelink, and repeated these on the previously built object file, and I got a hello.com file :tada:

3 Likes

The next steps for adding support would be:

  • figure out limitations:

    • exceptions (seem supported now)
  • add a couple targets for Cosmopolitan in Crystal:

    • x86_64-unknown-cosmo
    • aarch64-unknown-cosmo
  • write C bindings in src/lib_c/<target>;

    • check if errnos and the C defines being runtime symbols instead of compilation time constants has any adverse effects;
    • understand the impact the system abstractions (src/crystal/system); most of unix should work… but they might need to be specific to cosmocc?
    • understand supported OS features (e.g. epoll, kqueue); maybe we’ll have to differentiate the OS at runtime when needed;
    • figure out fiber context switches :sweat_smile:
  • link executable (for the current target)

  • codegen both object files (for both targets) in parallel + linker calls, at least for release builds;

Then are the external C libraries. I assume most should work as long as they’re compiled with cosmocc (x86_64 and aarch64). They should compile and behave as if they’d been compiled for POSIX :thinking:

I’m more reserved on Boehm GC, however… or not.

4 Likes

We can bind external runtime symbols, for example for errno values, but we can’t declare enum Errno that requires the value to be known at compilation time.

For example this compiles:

lib LibC
  fun printf(UInt8*, ...) : Int32
  $enosys = ENOSYS : Int32
end

LibC.printf("ENOSYS=%d\n", LibC.enosys)

But this won’t compile:

enum Errno
  ENOSYS = LibC.enosys
#          ^---
# Error: invalid constant value
end

Maybe we can fake it? Yeah, we can probably fake it with a struct wrapping an Int32 and acting as an enum, then Errno::ENOSYS = new(LibC.enosys).

Also we can’t call LibC::ENOSYS anymore (not really an issue).

2 Likes

Related links:

3 Likes