# GitHub - chung-leong/zigft: Zig function transform library

**URL:** <https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222>\
**Category:** Showcase\
**Tags:** language\
**Created:** [March 23, 2025, 11:36pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222 "2025-03-23T23:36:22Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [March 23, 2025, 11:36pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/1 "2025-03-23T23:36:22Z")

</div>

> **[GitHub - chung-leong/zigft: Zig function transform library](https://github.com/chung-leong/zigft?tab=readme-ov-file#zigft)**
>
> Zig function transform library

---

<div class="post-metadata">

**Author:** ![mnemnion](https://ziggit.dev/user_avatar/ziggit.dev/mnemnion/32/2478_2.png) [@mnemnion](https://ziggit.dev/u/mnemnion)\
**Post date:** [March 24, 2025, 3:54pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/2 "2025-03-24T15:54:02Z")

</div>

This is sophisticated stuff @chung-leong. Impressive.

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [March 25, 2025, 1:22pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/3 "2025-03-25T13:22:05Z")

</div>

I think some of my code belongs in the “most cursed Zig code” thread :-p

I’ve just add a new `close()` function for creating closures. I think this is relatively clean syntax:

```zig
const std = @import("std");
const fn_binding = @import("./fn-binding.zig");

pub fn main() !void {
    var funcs: [5]*const fn (i32) void = undefined;
    for (&funcs, 0..) |*ptr, index|
        ptr.* = try fn_binding.close(struct {
            number: usize,

            pub fn print(self: @This(), arg: i32) void {
                std.debug.print("Hello: {d}, {d}\n", .{ self.number, arg });
            }
        }, .{ .number = index });
    defer for (funcs) |f| fn_binding.unbind(f);
    for (funcs) |f| f(123);
}

```

```zig
Hello: 0, 123
Hello: 1, 123
Hello: 2, 123
Hello: 3, 123
Hello: 4, 123

```

---

<div class="post-metadata">

**Author:** ![neurocyte](https://ziggit.dev/user_avatar/ziggit.dev/neurocyte/32/3804_2.png) [@neurocyte](https://ziggit.dev/u/neurocyte)\
**Post date:** [March 26, 2025, 11:42am UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/4 "2025-03-26T11:42:18Z")

</div>

This is pretty freaking awesome.

---

<div class="post-metadata">

**Author:** ![Chaz](https://ziggit.dev/letter_avatar_proxy/v4/letter/c/4491bb/32.png) [@Chaz](https://ziggit.dev/u/Chaz)\
**Post date:** [March 26, 2025, 4:41pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/5 "2025-03-26T16:41:38Z")

</div>

The comptime version of .close() makes sense, and I see how a runtime version could make an interface/virtual function/abstract base class.

This sure looks like you made it work at runtime as a \*const fn (i32) void. That is amazing!

I’ve looked a few times and I’m still not able to follow how it works. I may not know enough to understand, I see the assembly code in there. Can I ask though, how this works?

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [March 26, 2025, 9:22pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/6 "2025-03-26T21:22:55Z")

</div>

For every function you’re binding to (or function type if you’re passing a function pointer), a “trampoline” is generated at comptime that merge the bound variables, stored in a Tupperware, with the received arguments to form the correct argument list for the original function. For example, if the original function has 3 arguments and the last one is bound, then trampoline would load a single value from the tuple’s address and place it in the register for the 3rd argument (r8 on x86-64). The first two arguments would already be in the right registers.

Now comes the tricky part, how you you provide the address of the tuple to the trampoline? How do you pass an argument without using the normal argument passing mechanism? The answer is through the stack. In the trampoline there’s a variable that’s deliberately left uninitiated. Assembly code asks for the address of this variable although it doesn’t do anything with it. Just three no-ops serving as a marker in the instruction stream.

Dynamically generated code writes the address of the tuple into this variable inside the trampoline just before it jumps into the trampoline. To determine the correct offset from the stack pointer I basically implemented a partial interpreter for each CPU architecture.

---

<div class="post-metadata">

**Author:** ![Chaz](https://ziggit.dev/letter_avatar_proxy/v4/letter/c/4491bb/32.png) [@Chaz](https://ziggit.dev/u/Chaz)\
**Post date:** [March 27, 2025, 3:24am UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/7 "2025-03-27T03:24:45Z")

</div>

Thank you for explaining this, it’s enlightening at a level that I haven’t worked with before.

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [March 27, 2025, 11:23am UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/8 "2025-03-27T11:23:30Z")

</div>

It’s very much an ugly hack. Ideally, we should be able to associate a local variable with a particular CPU register, like this in C (gcc only):

```c
register void *ptr asm ("rax");

```

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [November 20, 2025, 12:14pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/9 "2025-11-20T12:14:03Z")

</div>

The library has been updated for 0.15.x. The transform code didn’t require any change at all. The binding code had to be tweaked for the self-hosted backend. For some reason it’s using RAX for argument passing. Had to switch to a different register in the trampoline.

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [November 26, 2025, 11:22pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/10 "2025-11-26T23:22:19Z")

</div>

Eight months later, I just noticed how my iPhone had autocorrected “tuple” to “tupperware”. LOL 🤪

---

<div class="post-metadata">

**Author:** ![chung-leong](https://ziggit.dev/user_avatar/ziggit.dev/chung-leong/32/999_2.png) [@chung-leong](https://ziggit.dev/u/chung-leong)\
**Post date:** [October 5, 2026, 1:58am UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/11 "2026-10-05T01:58:22Z")

</div>

The code has just been updated to 0.17.0. While working on that, I noticed that the migration to 0.16.0 was actually unsuccessful. Function binding stopped working for functions with many arguments because the compiler has started putting AVX instructions into function prologues. My code wasn’t on the look out for the special VEX prefix so instruction decoding was totally messed up. Decoding x86 is really no fun 🤪

Anyway, the issue has been fixed for both 0.16.0 and 0.17.0.

---

<div class="post-metadata">

**Author:** ![elpablete](https://ziggit.dev/user_avatar/ziggit.dev/elpablete/32/7701_2.png) [@elpablete](https://ziggit.dev/u/elpablete)\
**Post date:** [October 5, 2026, 1:49pm UTC](https://ziggit.dev/t/github-chung-leong-zigft-zig-function-transform-library/9222/12 "2026-10-05T13:49:03Z")

</div>

And here I was thinking something like: “wow, cool name for a struct that packs arguments without spilling to another function call” 😅
