Stream: beginners

Topic: simple C platform example


view this post on Zulip Artur Swiderski (Dec 11 2023 at 20:57):

hi I am a bit stuck in my current project but for the next one, I will be definitely in need to create my own platform. I want to start gather information ahead to have relatively smooth start. Could you point me to simplest possible example of Roc platform done in "C" or "C++" ( I don't want to learn Rust or Zig just for that )

view this post on Zulip Shaiden Spreitzer (Dec 11 2023 at 21:18):

host.c - Put this file into ./platform

#include <stdlib.h>
#include <stdio.h>

void *roc_alloc(size_t size)
{
    return malloc(size);
}

void *roc_realloc(void *ptr, size_t new_size, size_t old_size, unsigned int alignment)
{
    return realloc(ptr, new_size);
}

void roc_dealloc(void *ptr, unsigned int alignment)
{
    free(ptr);
}

void *roc_memset(void *str, int c, size_t n)
{
    return memset(str, c, n);
}


struct RocStr { char *bytes; size_t len; size_t capacity; };

int main()
{
    struct RocStr str1;
    //roc__mainForHost_1_exposed_generic(&str1);
    roc__mainForHost_1_exposed(&str1); // identical!

    for (int i=0; i<str1.len && i<30; ++i)
        putchar(str1.bytes[i]);
    printf("\n");
}

main.roc - Also put this file into ./platform

platform "simple-c-test"
    requires {} { main : Str }
    exposes []
    packages {}
    imports []
    provides [mainForHost]

mainForHost : Str
mainForHost = main

Finally: app.roc - put this file into ./

app "app"
    packages { pf: "platform/main.roc" }
    imports []
    provides [main] to pf

zahl = 234

main : Str
main = "simple c example (this string must be long cuz we dont have sso!) \(Num.toStr zahl)"

1) Compile host.c: gcc -c platform/host.c
2) Compile app: ./roc build --no-link --optimize app.roc
3) Link all: gcc -o app app.o host.o

Edit:
4) Run: ./app

view this post on Zulip Artur Swiderski (Dec 11 2023 at 22:41):

this statement , I don't get it

main = "simple c example (this string must be long cuz we dont have sso!) \(Num.toStr zahl)"

view this post on Zulip Artur Swiderski (Dec 11 2023 at 22:57):

is this possible to expose more then just one object from Roc platform to "C" , let say provides [a1,a2,a3,a4] ??

view this post on Zulip Oskar Hahn (Dec 11 2023 at 23:01):

Currently not: https://github.com/roc-lang/roc/issues/6115

view this post on Zulip Brendan Hansknecht (Dec 11 2023 at 23:41):

You can only expose one function currently, but it can return anything, even a record of functions (though this does add some complexity for platform development)

view this post on Zulip Artur Swiderski (Feb 22 2024 at 11:36):

if I want to expose some interface from "C" to roc, let say there is some array with data on C side and I want to give Roc API to access those data how to do that ?

view this post on Zulip Artur Swiderski (Feb 22 2024 at 11:37):

Let say I have char [1000] buffer on "C" side and I want to read write to it

view this post on Zulip Artur Swiderski (Feb 22 2024 at 12:20):

I also want to expose to Roc normal C functions like for side effects how to do that

view this post on Zulip Brendan Hansknecht (Feb 22 2024 at 17:44):

There are a few options here:

  1. Add effects to read and write a single byte in the buffer. That will give direct mutable access to the buffer in roc.
  2. Copy the bytes into a roc list (can avoid the copy by using a seamless slice), pass that into roc, update it as necessary, return a roc list, and finally copy the result into the c buffer.
  3. Avoid using a C type for the buffer. Use a roc list instead. Then just pass that into roc directly. Return a roc list and use that from that point forward.

view this post on Zulip Artur Swiderski (Feb 22 2024 at 22:19):

@Brendan Hansknecht all great but what are exact syntax to pass anything from "C" to Roc

view this post on Zulip Artur Swiderski (Feb 22 2024 at 22:20):

in context of example provided by Shaiden Spreitzer

view this post on Zulip Brendan Hansknecht (Feb 22 2024 at 22:33):

If you are fine with just passing everything and then getting a result back (as opposed to using an effect). It would mean changing main/mainForHost to be a function in Roc. Then roc__mainForHost_1_exposed_generic would be passed args in the order (&output, in1, in2, in3, ...). So the location to write the output then each of the inputs.

view this post on Zulip Artur Swiderski (Feb 24 2024 at 11:17):

ok but how exactly syntax in "C" and .roc platform file looks like , what I should type there ?

view this post on Zulip Brendan Hansknecht (Feb 25 2024 at 17:36):

So I would personally advise using rust or zig, or maybe c++. Rust and zig have a well tested roc library for the types like RocStr. C++ has a newer library in a PR that isn't really tested, but may work. Zig would probably give you the simplest experience close to C with significantly more support and examples.

If you use C++ and the new library, I think that you can just import a fully working RocStr. Not 100% sure this code is correct, but something like this:

#include "roc_std.h"

// Other roc_alloc and such functions from above

int main()
{
    // Fill this as normal.
    char [1000] buffer;

    Roc::Str in(buffer);
    Roc::Str out;
    roc__mainForHost_1_exposed_generic(&out, &in);

    // out.content() should give you a `char *` if the output data. With out.length() as the length.
}

This assumes a Str -> Str main function.
So mainForHost : Str -> Str and

main : Str -> Str
main = \input ->
    ...

view this post on Zulip Artur Swiderski (Feb 26 2024 at 20:26):

hi I will consider switching later one but when I do "C" for now I can't link for some reason

#include <stdlib.h>
#include <stdio.h>

void *roc_alloc(size_t size)
{
    return malloc(size);
}

void *roc_realloc(void *ptr, size_t new_size, size_t old_size, unsigned int alignment)
{
    return realloc(ptr, new_size);
}

void roc_dealloc(void *ptr, unsigned int alignment)
{
    free(ptr);
}

void *roc_memset(void *str, int c, size_t n)
{
    return memset(str, c, n);
}


struct RocStr { char *bytes; size_t len; size_t capacity; };

int main()
{
    struct RocStr str1;
    struct RocStr str2;

    roc__mainForHost_1_exposed(&str1, &str2);

    for (int i=0; i<str1.len && i<300; ++i)
        putchar(str1.bytes[i]);
    printf("\n");
}
platform "simple-c-test"
    requires {} { main :  Str -> Str }
    exposes []
    packages {}
    imports []
    provides [mainForHost]

mainForHost : Str -> Str
mainForHost = main
app "app"
    packages { pf: "platform/main.roc" }
    imports []
    provides [main] to pf


main : Str  -> Str
main = \ lst ->
    "simple c example (this string must be long cuz we dont have sso!)"

view this post on Zulip Artur Swiderski (Feb 26 2024 at 20:30):

regardless if "C" struct is correct here or not , at least I expect it to link, btw. I want to pass list of floats as input not Str, but I want to make that work first

view this post on Zulip Brendan Hansknecht (Feb 26 2024 at 20:33):

Can't you expand on can't link?

view this post on Zulip Artur Swiderski (Feb 26 2024 at 20:43):

sorry I messed up my cmake and warnings in custom roc command were treated as errors

view this post on Zulip Artur Swiderski (Feb 26 2024 at 20:44):

so it works , what is the syntax when I want to pass list of floats , can I just pass

view this post on Zulip Artur Swiderski (Feb 26 2024 at 21:09):

the code I provided is malfunctioning I am not able to pass return value from function. For some reason I am getting garbage in str1, but when I pass it as in example provided by Shaiden Spreitzer it works

view this post on Zulip Artur Swiderski (Feb 26 2024 at 21:10):

maybe I will check this C++ library

view this post on Zulip Brendan Hansknecht (Feb 26 2024 at 21:12):

Sure, this should be what is required to pass a seamless slice of floats to roc. This avoids an extra allocation and copy of the list of floats.

This is not tested, I just typed it out and hopefully it is correct. Then on the roc side, just change the input to List F32.

struct RocList { float *data; size_t len; size_t capacity; };
const size_t SLICE_BIT = ((size_t)1) << (8 * sizeof(size_t) - 1);
const size_t REFCOUNT_MAX = 0;

int main()
{
    // Initialize this however you want with whatever length.
    float inputs[100];

    struct RocStr out;
    struct RocList in;

    in.data = inputs;
    in.len = 100;

    // This is not actually the capacity but something special for seamless slices.
    size_t rc = REFCOUNT_MAX;
    size_t slice_bits = (((size_t)&rc) >> 1) | SLICE_BIT;
    in.capacity = slice_bits;

    roc__mainForHost_1_exposed(&out, &in);

    // Everything else
}

view this post on Zulip Brendan Hansknecht (Feb 26 2024 at 21:14):

Is your planned output still a string?

view this post on Zulip Artur Swiderski (Feb 26 2024 at 23:30):

I don't know yet it may be list of float

view this post on Zulip Artur Swiderski (Feb 26 2024 at 23:42):

I am still working on my example and although I initialized input with something

#include <stdlib.h>
#include <stdio.h>

void *roc_alloc(size_t size)
{
    return malloc(size);
}

void *roc_realloc(void *ptr, size_t new_size, size_t old_size, unsigned int alignment)
{
    return realloc(ptr, new_size);
}

void roc_dealloc(void *ptr, unsigned int alignment)
{
    free(ptr);
}

void *roc_memset(void *str, int c, size_t n)
{
    return memset(str, c, n);
}


struct RocStr { char *bytes; size_t len; size_t capacity; };

int main()
{
    struct RocStr str1;
    char *zink  ="ankeanank";
    struct RocStr  str2 = { zink, 10, 10 };

    roc__mainForHost_1_exposed(&str1, &str2);

    for (int i=0; i<str1.len && i<300; ++i)
        putchar(str1.bytes[i]);
    printf("\n");
}

platform "simple-c-test"
    requires {} { main :  Str -> Str }
    exposes []
    packages {}
    imports []
    provides [mainForHost]

mainForHost : Str -> Str
mainForHost = main

app "app"
    packages { pf: "platform/main.roc" }
    imports []
    provides [main] to pf


main : Str  -> Str
main = \ lst ->
    "simple c example (this string must be long cuz we dont have sso!)"

after running I am getting garbage in str1
basically function is not returning anything

this is influence of str2 because as I mentioned if I create function which is just returning everything works just fine

view this post on Zulip Brendan Hansknecht (Feb 26 2024 at 23:51):

This is not a valid initialization of a RocStr:

    char *zink  ="ankeanank";
    struct RocStr  str2 = { zink, 10, 10 };

You could copy what I did above for a seamless slice, otherwise, RocStr is a more complex type. To initialize a big string, roc expects that the value is allocated on the heap and has the refcount stored on the heap before it. There is also an inline small string that is possible. This is why I don't really advise just hacking on things without a standard library.

With this current form, I am pretty sure that roc is attempting to modify the refcount of str2. This would live before *zink. As such, it is probably mutating and messing up str1 on the stack because that is what comes right before *zink. That is at least my guess as to why you get garbage.

view this post on Zulip Artur Swiderski (Feb 26 2024 at 23:53):

in your example you are using stack this is ok for list ?? float inputs[100];

view this post on Zulip Brendan Hansknecht (Feb 26 2024 at 23:59):

In my example I make it a seamless slice with this:

    size_t rc = REFCOUNT_MAX;
    size_t slice_bits = (((size_t)&rc) >> 1) | SLICE_BIT;
    in.capacity = slice_bits;

If you do the same to your string, it should work.

view this post on Zulip Artur Swiderski (Feb 27 2024 at 00:02):

in your example I don't see crash but I am not getting result in out is empty

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 00:03):

Hmm. I guess I'll need to take a deeper look.

view this post on Zulip Artur Swiderski (Feb 27 2024 at 00:06):

it does not work either

    char *zink  = (char*)malloc(10);
    size_t rc = REFCOUNT_MAX;
    size_t slice_bits = (((size_t)&rc) >> 1) | SLICE_BIT;


    struct RocStr  str2 = { zink, 10, slice_bits };

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 00:07):

Oh, this is a different bug. No idea what actually causes this. In the platform main, try this:

mainForHost : Str -> Str
mainForHost = \x -> main x

view this post on Zulip Artur Swiderski (Feb 27 2024 at 00:15):

memory violation on both my and your example

view this post on Zulip Artur Swiderski (Feb 27 2024 at 00:17):

I will try c++ library

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 00:20):

Oh, minor typo:
roc__mainForHost_1_exposed should be roc__mainForHost_1_exposed_generic. I think that gets it working

view this post on Zulip Artur Swiderski (Feb 27 2024 at 00:40):

still crashing , I tried c++ version in meantime and there is problem with name mangling for example it can't find "roc__mainForHost_1_exposed_generic" , could you point out zig example but zig is unfortunate because I have to use C anyway for other reasons so there will be mess

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 00:49):

Hmm....I ran it just fine

view this post on Zulip Artur Swiderski (Feb 27 2024 at 00:50):

could you copy paste your entire thing maybe I messed up something
and linking command would be nice to have too maybe I messed up something there

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 01:01):

This works for me letting roc handle the linking: https://gist.github.com/bhansconnect/f5445239cd0e614514c9f49333e24e10

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 01:02):

Note, github doesn't let me put / in the names so have to put platform-main.roc as platform/main.roc

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 01:02):

Then just roc build app.roc

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 01:04):

It also works to build manually by doing:

roc build --no-link app.roc
gcc -o app platform/host.c app.o

view this post on Zulip Artur Swiderski (Feb 27 2024 at 01:45):

thx I will look

view this post on Zulip Artur Swiderski (Feb 27 2024 at 01:54):

ok I had short Str and for some reason it was crashing entire thing thx for help

view this post on Zulip Artur Swiderski (Feb 27 2024 at 02:10):

now I looked at your example and everything works fine short and long

view this post on Zulip Brendan Hansknecht (Feb 27 2024 at 02:11):

Awesome


Last updated: Jul 06 2025 at 12:14 UTC