Stream: platform development

Topic: Set the refcounter in a platform


view this post on Zulip Oskar Hahn (Jan 27 2024 at 17:29):

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?

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:30):

9223372036854775808 means that you have a refcount of 1.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:31):

Yeah...its a bit weird

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:32):

If you give something with a refcount of 1 to roc, roc will deallocate it.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:34):

Honestly, I am not actually sure if we need that oddity anymore.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:35):

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.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:36):

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.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:36):

Anyway, just consider the refcount to be counting form the minimum isize to the maximum isize and the numbers all work out currently.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:37):

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.

view this post on Zulip Oskar Hahn (Jan 27 2024 at 17:44):

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?

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:47):

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.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:48):

do you have source I can double check?

view this post on Zulip Oskar Hahn (Jan 27 2024 at 17:49):

Sorry, I added the debug-statement in roc_realloc instead of roc_dealloc.

Yes, the value gets deallocated by roc.

view this post on Zulip Oskar Hahn (Jan 27 2024 at 17:55):

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.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:57):

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).

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 17:58):

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

view this post on Zulip Oskar Hahn (Jan 27 2024 at 18:01):

I will come back to this, as soon as I understand seamless slice :sweat_smile:

view this post on Zulip Oskar Hahn (Jan 27 2024 at 18:06):

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?

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 18:07):

Yep. Otherwise, it would leak.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 18:08):

well, you check if it is 1. Cause if it is 1, the platform has the only reference to it.

view this post on Zulip Oskar Hahn (Jan 27 2024 at 18:17):

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

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 18:25):

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.

view this post on Zulip Brendan Hansknecht (Jan 27 2024 at 18:26):

Does the simple-webserver-platform do this?

Glue and rust drop functions should be dealing with it.

view this post on Zulip Oskar Hahn (Jan 28 2024 at 13:16):

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