Passing context through foreign calling conventions - is this [not] working as intended?

There’s a good chance I just didn’t interpret this feature correctly. I expected that the runtime offers a way to pass the context indirectly even when a non-odin calling convention function is involved, through tread-local storage or equivalent technique (in fact I expected the context to be fully implemented as TLS, and not passed in RCX, but well).

For example, if I want to use it in a stdcall WNDPROC, I interpreted the docs such that the only way to go about this is by calling default_context, but that literally initializes a new context with all default values. Which is not my intention, I want to smuggle the entire thing through all the win32 functions on the call stack.

Is that not a thing? I thought it would work that way, since it partially already uses TLS anyway for arena allocator data, so why not go the full length.

Also (tangent), codegen when not using -o:speed for context = runtime.default_context() sure is a bit odd, zeroing, initalizing, and then copying a fresh context twice back to back, which looks a bit amusing in _cleanup_runtime_contextless – this is actually where I came from; I was debugging a sporadic access violation from within os.exit; I’m still honestly unsure how that works, since that function will just create a new default context and then clean that, rather than using the one passed over the odin calling convention.

1 Like

Afaik, you can’t use TLS memory for “passing” the context around because modifications to the context in one scope should not be visible outside of it.

  {
    context.allocator = virtual.arena_allocator(&arena)
    something_that_allocates()
    virtual.arena_free_all(&arena)
  }
  // context.allocator is default allocator again.

If context would be a pointer to thread local var, you’d have no knowledge across DLL boundaries if context should be restored or not, thus each odin callconv procedure must explicitly save a copy and restore it.

In this case, the compiler only have to do a stack copy inside a procedure when it modifies context.

Why? Is it to share allocators down the line? Give a bit more context please.

If the contextless call allows you to pass void *userarg you could just pass a ^runtime.Context in and restore context from there.

If not, you can only smuggle things with your own global @(thread_local) ctx: runtime.Context value.


This is just me, but I’d probably have a simple guard to make sure I update ctx to the current one.

@(deferred_none=reset_temp_context)
temp_context :: proc() {
	ctx = context
}

@(init)
reset_temp_context :: proc() {
	ctx = {}
	ctx.allocator      = runtime.panic_allocator()
	ctx.temp_allocator = runtime.panic_allocator()
}

main :: proc() {
	{
		temp_context()
		foreign_call(callback)
	}
}

Hey, thanks for your reply!

Afaik, you can’t use TLS memory for “passing” the context around because modifications to the context in one scope should not be visible outside of it.

That’s really orthogonal to TLS or not. I’m aware of the locality of context changes. Basically, it could just be pushing onto / popping from a stack.

My question isn’t really about being able to achieve context passing in other ways. Most APIs nowadays have been designed with enough foresight to include a fat pointer or void *context or else. But not all! Sometimes I still need to work around limitations of badly designed proprietary lab equipment drivers and such.

Really, this question is just me being unsure which interpretation of “reading the Odin docs between the lines” is correct.


The example in the docs about glfw.SetErrorCallback here:
When I read it the first time I (completely arbitrarily) understood it the following way:

In this example, the context is not passed explicitly via the odin calling convention, so then have to do extra work to get it, but it’s still there. The context would be taken from somewhere up the call chain on thread that calls the callback.

But as it turns out, that’s false, and instead it’s the following:

In this example, the context is lost, but since fmt.println depends on it, you need to make sure to initialize a new context. Getting the new context to do the right thing for your application is your problem.


Different languages and frameworks implement this concept in different ways, and it just so happened that my brain was arbitrarily leaning towards the other way when I read that.