(a)RManos Blog

Looksmaxxing Gremlins with Odin

How to create complete bindings in minutes for every C library

  ·   7 min read

Intro

After two weeks in Odin, it is time to find what libraries I can use for backend development, and I found none.

To be honest, I was expecting the disappointment.

Odin does not have libraries for backend, expect a small incomplete framework for HTTP, called “odin-http”.

For that reason, I went looking for all the libraries in C for backend development, like Database drivers, HTTP frameworks, Websocket, WebRTC etc.

But there is a problem with the Odin language, it does not have a built-in translator from C to Odin, like Golang and Zig.

The community does not believe in C translators because the majority of them are game/GPU developers and most of the libraries that they use are not difficult to bind. Their libraries use basic data types.

On the contrary, backend libraries possess peculiar types that originate from the intricacies of the Operating System, as well as a multitude of structures containing macros and functions. For that reason, most of Golang developers move to Zig that has the best automatic C to Zig translator.

Nevertheless, the issue lies not solely in the magnitude of these libraries, but also in their appearance to me.

The face of C

Call me a langist (= like a racist but for programming languages), but C is ugly.

It is so ugly, that I can not read it.

This is what C code looks like

Image alt

I cannot spend hours on creating bindings for a library that looks like this.

Thankfully, there is a solution to the problem.

Runic to the rescue

Runic created by a smart indivindual who wanted a CLI tool to generate bindings for C libraries to Odin.

We will use Runic to create bindings for Libuv. Libuv is a high level library for asynchronous IO. It is the only library that will help Odin compete with Rust in backend development.

Now I will show you a Runic’s configuration file to create bindings for Libuv (that I curently installed through package manager).

You will see that the structure of the configuration is so easy, that does not need explanations.

version: 0
platforms:
  - Linux x86_64
from:
  language: c
  static: "libuv.a"
  headers: 
     - "/usr/include/uv.h"
  includedirs:
    - "/usr/include"
to:
  language: odin
  package: uv
  trim_prefix:
    functions: uv_
    types: Uv_
    constants: UV_
  no_build_tag: yes
  use_when_else: yes
  ignore_arch: yes
  out: "uv-normal/uv.odin"

This configuration file will create a “uv-normal” folder with “uv.odin” file containing most of the Structs and procedures of Libuv.

I said most because it does not work properly for complicated libraries like Libuv.

The generated bindings contain incomplete structures that can cause crashes or other undefined behaviours.

If you are lucky you will get a crash like that.

Fatal glibc error: malloc.c:2599 (sysmalloc): assertion failed: (old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)
Aborted (core dumped)

If you are unlucky, the library may not even work as expected. I didn’t get any error which type were wrong or missing. For example, I translated a Libuv example to Odin, and it was stalling because the size of an Odin generated type from “uv.odin” was smaller than the actual type of the C library.

At that point, I was really devastated for three days, thinking of all the hard work that I have to do as a lazy person … until I saw a dream.

In my dream, Trump showed up at an Odin conference ranting, “We will deport all the macros, and we will build the biggest header file on earth. Who will pay for the header file? Yes! GCC will pay for the header file!”

Trump Runic

Trump was right! Runic cannot generate bindings successfully because macros sabotage its work.

For that reason, we will make GCC to deport all the macros from Libuv header file and create a large new header file with everything inside for Runic to parse.

gcc -E /usr/include/uv.h > uv-preprocessed.h

The new header file contains all the types and functions used by Libuv, even the ones from other libraries but used in the Libuv..

Now lets update the Runic configuration file to generate bindings from the preprocessed header file in the “uv-preprocessed” folder.

version: 0
platforms:
  - Linux x86_64
from:
  language: c
  static: "libuv.a"
  headers: 
     - "uv-preprocessed.h"

to:
  language: odin
  package: uv
  trim_prefix:
    functions: uv_
    types: Uv_
    constants: UV_
  no_build_tag: yes
  use_when_else: yes
  ignore_arch: yes
  out: "uv-preprocessed/uv.odin"

This time the “uv.odin” file contains literally all the types and functions of the header file, but also some errors.

Runic generated these types that don’t make any sense. Maybe it hallucinates like an AI… anyways, delete them.

struct (anonymous at /usr/include/netinet/tcp.h:97:5) :: ^^^rawptr
struct (anonymous at /usr/include/netinet/tcp.h:112:5) :: ^^^rawptr
union (anonymous at /usr/include/netinet/tcp.h:96:17) :: ^^^rawptr

The generated code has 6000 lines, which is too much. For that reason, we will delete all procedures that do not start with “uv_”. Furthermore, procedures that do not commence with “uv_” will not be executed due to the absence of “foreign import”.

After the cleanup, if you try to compile the library with an example, then you will see some errors for missing types. Thankfully, the missing types can be dealt with effortlessly. Furthermore, I am genuinely thankful for getting errors for the missing types!

For example, based on this error message, we can delete “va_list” (because no procedure or struct needs it) and copy the “uv_getnameinfo_s” type from our previous generated code in “uv-normal” (or create it by hand).

uv.odin(149:12) Error: Undeclared name: __gnuc_va_list 
        va_list :: __gnuc_va_list 
                   ^~~~~~~~~~~~~^ 
 
uv.odin(1470:21) Error: Undeclared name: uv_getnameinfo_s 
        uv_getnameinfo_t :: uv_getnameinfo_s 
                            ^~~~~~~~~~~~~~~^ 

Now if you want you can delete more types that don’t contain “uv_” but be careful. After each deletion, check for compilation errors.

Copy the refactored “uv.odin” from “uv-preprocessed” folder and put it at the base of the project library.

When it starts compiling successfully, the size of the types will be 100% correct (maybe). Just to be certain, always have a C and Odin code that will print the size of each type so you can easily compare between them.

I used the same process for Libcurl as well and worked pretty fine.

You can download the Libuv bindings from https://github.com/rm4n0s/libuv-odin

Summerized steps

  1. Preprocess the header file with “gcc -E”

  2. Create two Runic configuration files

    • one for the preprocess header file,
    • and another one for the original header file.
    • also they need to have different output paths.
  3. Run Runic for both configuration files

  4. Edit the bindings from the preprocessed header.

    • Delete hallucinations
    • Fix all the errors,
    • Remove all the procedures that do not start with the library’s name.
    • If important types are missing, then copy them from Runic’s output generated from the original header.
    • If important types are missing, and you can’t find them anywhere else, then write them by hand
  5. Copy the bindings from the preprocessed header to the base folder of your library’s project.

  6. Create examples that call the bindings.

Very simple steps to Make Odin Great Again!

List of things that I learned

  • The three dots in C equals to ‘#c_vararg args: ..any’. You need to remember that because Runic doesn’t translate ‘…’ to Odin.
  • The va_list exists in the core:c library
  • Runic will not translate inline and macro functions to Odin
  • Runic translates all “char *” to “cstring” but “cstring” means “char const *”
  • “char *” is [^]byte and [^]u8
    • use make() to allocate [^]byte but free() or delete(buf[:size]) to deallocate it
  • memcpy(dst, src, size) translates to copy(dst[:size], src[:size])

The face of bindings

Now, you may wonder … how the C library looks like through Odin bindings? Click to see the face of our bindings.

Click me

Image alt

Damn! it is still ugly.

Maybe because these bindings do not have Odin’s context and error handling.