I don't fully understand the ref counter. What I am trying to do is to allocate a string in the platform and give it to roc as an function argument.
When I understand it correctly, I have to allocate another usize value before the actual data. But what value do I have to use?
The roc implementation uses (this code)[https://github.com/roc-lang/roc/blob/40026bf78edd22e0b3412657a7bbb879fa2bd2be/crates/compiler/builtins/bitcode/src/utils.zig#L461]. I can't read this code:
pub const REFCOUNT_ONE_ISIZE: isize = std.math.minInt(isize);
pub const REFCOUNT_ONE: usize = @as(usize, @bitCast(REFCOUNT_ONE_ISIZE));
But when I print the value, I get 9223372036854775808
. What does this number mean?
When a platform gives a string with a refcount of 1 to roc, who has to deallocate it? Does roc decreases the refcount to 0 and deallocates it or does the platform has to deallocate the memory after the roc-function returns?
9223372036854775808
means that you have a refcount of 1.
Yeah...its a bit weird
If you give something with a refcount of 1 to roc, roc will deallocate it.
Honestly, I am not actually sure if we need that oddity anymore.
It was chosen due to having the first bit set/not set is an easy to check piece of information. That said, I think we now just explicitly check if the refcount is one anyway.
I know there was talk of storing extra info in a bit of the refcount, but currently we don't so :shrug: Probably could be better.
Anyway, just consider the refcount to be counting form the minimum isize to the maximum isize and the numbers all work out currently.
All that said, if you have a byte slice in zig that zig controls the ownership of, there is now a nicer way to pass it into roc that can avoid any allocation.
Ok. So I just use this number.
When I do so, the value does not gets deallocated automatically. Can I just deallocate it, after the roc-function returns, or do I have to check the refcount? Since the roc function is pure, I would guess, that the argument can not be stored somewhere in roc, so it should be safe to deallocate it. Is this correct?
When I do so, the value does not gets deallocated automatically.
That definitely would be a bug of some sort if the value isn't referenced in any way by the output.
do you have source I can double check?
Sorry, I added the debug-statement in roc_realloc
instead of roc_dealloc
.
Yes, the value gets deallocated by roc.
After this information, I think the go function to create a roc string, that we used for the false-interpreter, was wrong: https://github.com/ostcar/roc-examples/blob/f07c2ed105130ce0a41e066f3ac2a2e6a7a14f41/examples/false-interpreter-go/platform/main.go#L36-L43
The correct implementation is
func rocStrFromStr(str string) C.struct_RocStr {
// TODO: 8 only works for 64bit. Use the correct size.
refCountPtr := roc_alloc(C.ulong(len(str)+8), 8)
refCountSlice := unsafe.Slice((*uint)(refCountPtr), 1)
refCountSlice[0] = 9223372036854775808 // TODO: calculate this number from the lowest int
startPtr := unsafe.Add(refCountPtr, 8)
var rocStr C.struct_RocStr
rocStr.len = C.ulong(len(str))
rocStr.capacity = rocStr.len
rocStr.bytes = (*C.char)(unsafe.Pointer(startPtr))
dataSlice := unsafe.Slice((*byte)(startPtr), len(str))
copy(dataSlice, []byte(str))
return rocStr
}
The old implementation was nice, since it did use the actual memory of the go-string. But a copy is necessary to add the space for the refcount.
Assuming Go keeps a reference to the string alive somewhere a seamless slice could be used instead. Just would means that Roc will never free the string (which depending on the use case may matter).
For example, in a platform I am currently working on, I do this for a list:
https://github.com/bhansconnect/roc-fuzz/blob/a5e4053a1be6d0f9a9ca5cef26139230e8a81f2d/platform/host.cpp#L293-L296
I will come back to this, as soon as I understand seamless slice :sweat_smile:
When I call a roc-function, that returns some heap-value, for example a RocStr. Does the platform has to check the refcount and if its 0 deallocate it?
Yep. Otherwise, it would leak.
well, you check if it is 1. Cause if it is 1, the platform has the only reference to it.
Does the simple-webserver-platform do this? I can not find anything like this here: https://github.com/roc-lang/basic-webserver/blob/39853f523f9fd162ea6d9920209912fd3e478e59/platform/src/server.rs#L70C23-L89
It would be nice, if roc would export a function, that accepts a pointer to any roc type, checks all the refcounts and deallocates if necessary. In other case it is hard to do it manually for complex types like a Response, that contains a lot of different heap-values.
Ok. Response only has two values on the heap: https://github.com/roc-lang/basic-webserver/blob/39853f523f9fd162ea6d9920209912fd3e478e59/platform/InternalHttp.roc#L39
But there could be other types, that are much more complex. Like a Dict Str Str
It would be nice, if roc would export a function, that accepts a pointer to any roc type, checks all the refcounts and deallocates if necessary. In other case it is hard to do it manually for complex types like a Response, that contains a lot of different heap-values.
Yeah, glue is planned to support this. That said, when roc finally support exporting multiple functions, it should be easy to make a deallocate function if wanted. Just MyType -> {}
that the platform uses.
Does the simple-webserver-platform do this?
Glue and rust drop functions should be dealing with it.
I have another question on this topic.
I try to do something similar like the wasm4 platform. The roc code first returns a Box Model
, and any other call to roc has this model as an argument.
What do I have to do with the refcount? I have an update
-like function. The returned model has a refcount of 1, it does not matter, what the ref count was before.
Then I have a "read"-function, that can not manipulate the model. When I call it, the refcount get reduced by one.
My current solution is, to set the refcount to 2 before calling the "read"-function. Is this the correct way to do it or is there another way to tell roc, not to deallocate an object?
Last updated: Jul 06 2025 at 12:14 UTC