Skip to content

Feat: Expose caller information to foreign functions#1215

Open
thaolt wants to merge 1 commit into
wren-lang:mainfrom
thaolt:main
Open

Feat: Expose caller information to foreign functions#1215
thaolt wants to merge 1 commit into
wren-lang:mainfrom
thaolt:main

Conversation

@thaolt

@thaolt thaolt commented Jan 4, 2026

Copy link
Copy Markdown

This change enhances the foreign function interface by allowing foreign functions to inspect their call site within the Wren VM.

By saving the VM's state with STORE_FRAME() before a foreign call, the foreign function can now access the caller's context, including the instruction pointer. This allows for runtime reflection, such as determining the method name that was used to invoke the foreign function by inspecting the bytecode.

This enables more advanced use cases for foreign functions that require information about their invocation context. The LOAD_FRAME() macro ensures that the VM state is correctly restored after the foreign function returns, maintaining the integrity of the interpreter.

Also, it breaks no test.

@mhermier

mhermier commented Jan 4, 2026 via email

Copy link
Copy Markdown
Contributor

@thaolt

thaolt commented Jan 4, 2026

Copy link
Copy Markdown
Author

I’m surprised and glad to see someone respond 🙂

My intention is to provide a convenient way to use libFFI in my app. I was experimenting with something like this:

foreign class Raylib is FFI {
    #!extern(dll="raylib", args="int,int,char*")
    foreign static InitWindow(width, height, title)

    #!extern(dll="raylib")
    foreign static BeginDrawing()

    #!extern(dll="raylib")
    foreign static EndDrawing()

    #!extern(dll="raylib")
    foreign static WindowShouldClose()

    #!extern(dll="raylib")
    foreign static CloseWindow()
}

My first attempt was to parse these attributes at bind time, but I discovered that the compiler does not emit class attributes until END_CLASS.

The next approach I tried was parsing the attributes at runtime. I bind all foreign methods that extend the FFI class to a single C function. However, I couldn’t find a way to extract the calling method name from apiStack as it only provides the module and class name. That’s why I made changes to allow access to the bytecode data and the instruction pointer so I could extract the calling method name. Roughly like this:

void executeFunctionFn(WrenVM *vm) {
    // ...
    // get the current frame
    CallFrame* frame = &vm->fiber->frames[vm->fiber->numFrames - 1];
    ObjFn* fn = frame->closure->fn;

    uint16_t methodSymbol =
        (uint16_t)((*(frame->ip - 2) << 8) | *(frame->ip - 1));
    fprintf(stderr, "Method symbol: %d\n", methodSymbol);

    // Get the actual method name from the VM using the symbol
    if (targetClass != NULL) {
        methodName = vm->methodNames.data[methodSymbol]->value;
    }

    fprintf(stderr,
            "Executing foreign method %s.%s.%s\n",
            moduleName, className, methodName);
    // ...
}

This produces output like:

...
0: raylib.Raylib (classObj: 0x5f800102f6f0)
=== End FFI Classes ===
Method symbol: 176
Executing foreign method raylib.Raylib.InitWindow(_,_,_)
Method symbol: 177
Executing foreign method raylib.Raylib.BeginDrawing()
Method symbol: 178
Executing foreign method raylib.Raylib.EndDrawing()
Method symbol: 179
Executing foreign method raylib.Raylib.CloseWindow()

I’ll get back with a benchmark later, it’s only been a day since I started working with the codebase, and I’m still getting familiar with it. I’m also not sure whether it’s intentional that STORE_FRAME and LOAD_FRAME are not used when calling a foreign method.

@mhermier

mhermier commented Jan 4, 2026 via email

Copy link
Copy Markdown
Contributor

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

Successfully merging this pull request may close these issues.

2 participants