I watched the [talk]https://www.youtube.com/watch?v=zMRfCZo8eAc from @Folkert de Vries at software you can love and it was fascinating. My question is, what would be necessary to change in roc, to make a pattern like this more accessible for other platforms.
There is already the idea to pass around an allocator. Here is the issue.
With this change, it would be possible for roc_alloc to save an error to the context, so that when roc crashes, you could find out, that roc has to be called again with more memory.
But there still is the problem, that roc would just crash, when roc_alloc returns a zero pointer.
What do you think about the idea, that roc would not crash, but returns that error to the caller?
This would mean, that functions like roc__mainForHost_0_caller or roc__mainForHost_1_exposed_generic would need to return something. I guess, that an int value would be sufficient. A problem would be roc__mainForHost_1_exposed that already returns a value. But maybe, this function could be removed in favour of always using roc__mainForHost_1_exposed_generic.
@Folkert de Vries do you think with the allocator idea and an error-value from roc_alloc it would be possible to build a platform, that never allocates without longjmp?
that would not just work. Without longjump, you'd have to do what is called unwinding the stack: bubble the error upwards until you catch it at some point
this is similar to the fallible allocation in rust: what do you do when you run out of memory? likely the thing you attempted was fine, and the excessive memory usage happened much earlier and is not the fault of the allocation that broke the camel's back
When I understand your talk correctly, then you are basically calling:
when setjump(ctx) is
0 ->
threadlocal_memory_bucket = small_memory_bucket
roc__mainForHost_1_exposted_generic( ...)
1 ->
log()
threadlocal_memory_bucket = bigger_memory_bucket
roc__mainForHost_1_exposted_generic(...)
roc_alloc(...) {
ptr = alloc_from_bucket(threadlocal_memory_bucket )
if ptr == 0 then
longjump(ctx, 1)
else
return ptr
}
When roc__mainForHost_1_exposted_generic would return 1, when any call to roc_alloc returns 0, then this should do the same:
threadlocal_memory_bucket = small_memory_bucket
err = roc__mainForHost_1_exposted_generic(...)
if err == 1 then
log()
threadlocal_memory_bucket = bigger_memory_bucket
roc__mainForHost_1_exposted_generic(...)
I think it is not necessary to unwind the stack, when all functions just return an error code as a value.
I you start roc__mainForHost_1_exposted_generic with an empty bucket, then the excessive memory usage has to be somewhere in roc. You use longjmp to call roc again, but shouldn't it be possible to do the same thing by returning an error code as a value?
Is there something I misunderstand?
well, you still pass through all the stack frames if they all return right? But yes if you still do the memory management with the buckets then that can work on a technical level. A complication is that all functions then do need to return an error code, also any stdlib function that can (transitively) allocate
So this is the idea. And since @Brendan Hansknecht has to touch all functions anyway to include the allocator, maybe he could also add a return value :sweat_smile:
Do you want it to be cleaning up the stack and all roc allocations as it returns? Basically like a full exception unwind in c++?
If the platform knows all the resources roc could have allocated, panic + longjmp + cleanup will likely be much much faster. Also simply crashing for platforms that want it will be much faster as well.
If the platform doesn't know the resources roc could allocate. This could make sense as a more proper clean up. Though you probably would want to do it with exceptions and libunwind to minimize costs to the happy case.
I am not familiar with the necessary code. Maybe what I am thinking about is not possible.
What I am thinking about is no exception at all, but to return an error as a value. Like a Result a [OutOfMemory] in roc, or an (foo, error) in go.
Sure, but my two questions are:
The goal is to get a zero-allocation platform, but without longjmp. For example, I don't think longjmp is possible in a go platform. If the memory bucket is full, then I have to inform the callside somehow, that roc has to be restarted with a bigger bucket. So instead of a crash, an "OutOfMemory" error would be nicer.
It is probably saver, if roc would cleanup everything it has access to. But I am not sure if this is really necessary.
I see....hmmm :thinking:
I totally get the goal, but my gut feeling is that it isn't something we would want to support. Crashes are meant to be exceptional and are really better handled with exception related mechanisms like longjmp and libunwind. Those would have a much smaller perf impact on the common case of no exception being raised.
Like if we were to implement this is roc today, I think we would implement it via setjmp and longjmp.
Then just return a final error
I also think that unwinding for most platforms wouldn't be needed. The platform likely can use an arena for roc and track the resources that roc opens. As such, the platform can quickly reset the arena and free all the resources if roc happens to crash. No need for any slow unwind.
Ok. I get it. I though the performance is not an issue, since an OutOfMemory does not happens often. But when I understand you correctly, then the performance is worth in the happy case. I get, that this would be bad.
What I don't get is, why @Folkert de Vries needed longjmp. Why couldn't he use the solution you propose? I though he sad in his talk, that a unwind above ffi is a problem. But you are saying, that it does not even need an unwind?
I would need to look at NEA again to remember all of the details. That said, my proposed solution would use longjmp still.
For unwind, yeah, it doesn't necessarily work over ffi. So it would need to be done within roc. Essentially for unwind, to work, it would be something like:
setjmp/longjmp was simpler and faster. But also it's dangerous and cursed
100%
Though I don't know enough about libunwind to know how cursed it is under the hood
Last updated: Jun 16 2026 at 16:19 UTC