Stream: platform development

Topic: generated rust glue seems different from whats in basic-cli


view this post on Zulip John Murray (Dec 15 2023 at 23:08):

I have a platform I'm trying to setup to make a property based testing framework for roc.

I stubbed out the basics here

I would like to write some functions in rust that would be exposed to roc to get random strings, nums, etc. I gathered from reading some of the other platform examples i need to make roc_fx_<func> defs and add them to an Effect.roc file manually.

The issue im running into is any of the custom effects I added seem to cause the roc app to exit when called. If i stick to "pure" roc the app seems to run normally. I also copied some effects from basic cli to see if was how my functions were defined but seems to have the same issue.

I used roc glue <path to rust spec> to generate the glue code but one difference I'm noticing between the glue it generated and the basic cli is how roc__mainForHost_xxxx links are handled.

The glue generated this

#[repr(C)]
#[derive(Debug)]
pub struct RocFunction_72 {
    closure_data: Vec<u8>,
}

impl RocFunction_72 {
    pub fn force_thunk(mut self) -> roc_std::RocResult<(), i32> {
        extern "C" {
            fn roc__mainForHost_0_caller(arg0: *const (), closure_data: *mut u8, output: *mut roc_std::RocResult<(), i32>);
        }

        let mut output = core::mem::MaybeUninit::uninit();

        unsafe {
            roc__mainForHost_0_caller(&(), self.closure_data.as_mut_ptr(), output.as_mut_ptr());

            output.assume_init()
        }
    }
}

pub fn mainForHost() -> roc_std::RocResult<(), i32> {
    extern "C" {
        fn roc__mainForHost_1_exposed_generic(_: *mut roc_std::RocResult<(), i32>, );
    }

    let mut ret = core::mem::MaybeUninit::uninit();

    unsafe {
        roc__mainForHost_1_exposed_generic(ret.as_mut_ptr(), );

        ret.assume_init()
    }
}

but in basic-cli i see

extern "C" {
    #[link_name = "roc__mainForHost_1_exposed_generic"]
    pub fn roc_main(output: *mut u8);

    #[link_name = "roc__mainForHost_1_exposed_size"]
    pub fn roc_main_size() -> i64;

    #[link_name = "roc__mainForHost_0_caller"]
    fn call_Fx(flags: *const u8, closure_data: *const u8, output: *mut u8);

    #[allow(dead_code)]
    #[link_name = "roc__mainForHost_0_size"]
    fn size_Fx() -> i64;

    #[link_name = "roc__mainForHost_0_result_size"]
    fn size_Fx_result() -> i64;
}

Do i need to manually create some of these other extern funcs like roc_main_size, call_Fx, etc for roc to handle the effects correctly?

I couldn't find any platform making tutorial docs when poking around, do any exist? If not Id be happy to document some of the process to get the basics setup once I get something working

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:29):

The docs don't exist cause we are working on a huge change (have been for a while). So documenting the process now isn't super useful.

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:30):

As for the other functions there, you shouldn't need them. Glue should generate that correctly.

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:30):

You will need the roc_fx_* function which I don't think glue will generate

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:31):

Probably would need to look at your platform specifically when I am on pc to give real advice

view this post on Zulip John Murray (Dec 15 2023 at 23:33):

Ahh the doc stuff makes sense.

I tried to split up some of my rust code around so definitely possible I missed something during that

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:35):

Yeah, first glance on phone looks fine.

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:36):

The size function isn't needed cause the size of a task i32 is know at compile time

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:36):

So glue can just generate all of that statically

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:37):

And the caller function labeled call_Fx is used in the force_thunk function that glue generates`

view this post on Zulip Richard Feldman (Dec 15 2023 at 23:39):

@John Murray btw I'd love to include property based testing directly into expect! Want to chat about it sometime?

view this post on Zulip John Murray (Dec 15 2023 at 23:41):

Definitly! mostly using this as an excuse to make my own platform so would love to help out with pbt in expect

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:45):

I'll try to take a look shortly, probably something simple. If you want to try and debug in the meantime, run with a debugger and print the stack trace when it crashes.

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:46):

That will probably reveal a function with slightly off API or something

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:47):

Oh, I bet this needs an explicit out param:

#[no_mangle]
pub extern "C" fn roc_fx_genStr() -> RocStr {
    RocStr::from("abc")
}

view this post on Zulip Brendan Hansknecht (Dec 15 2023 at 23:47):

out: *mut RocStr

view this post on Zulip John Murray (Dec 15 2023 at 23:55):

so would it be something like

#[no_mangle]
pub extern "C" fn roc_fx_genStr(_out: *mut RocStr) -> RocStr {
    RocStr::from("abc")
}

or would i need to set out to RocStr::from("abc")?

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 00:03):

Set out to that and return nothing

view this post on Zulip John Murray (Dec 16 2023 at 02:26):

I updated to

#[no_mangle]
pub extern "C" fn roc_fx_genStr(out: *mut RocStr) {
    unsafe {
        out.write(RocStr::from("abc"));
    }
}

with this roc program to test

app "simple"
    packages { pf: "../platform/main.roc" }
    imports [
        pf.Task.{Task},
        pf.Generator,
        pf.Stdout,
    ]
    provides [main] to pf

dbge = \x ->
    dbg x

    x

main : Task {} I32
main =
    val <- Generator.genStr |> dbge |> Task.await
    {} <- Stdout.line val |> dbge |> Task.await
    Task.err 0

and the output I have is

roc dev examples/simple.roc
🔨 Rebuilding platform...
ENTERTING ROC
[examples/simple.roc:11] x = <opaque>
LEAVING ROC
RES:(RocOk(ManuallyDrop { value: () }))

the last line sometimes will be something like RES:(RocErr(ManuallyDrop { value: -1597949984 })) were the number changes a lot.

Would that imply that maybe my manual bindings for things like alloc are wrong?

view this post on Zulip John Murray (Dec 16 2023 at 04:36):

I ended up getting it to work but not using the generated glue in roc_app and instead using the same rust_main from basic cli

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 04:40):

I wonder what glue has wrong

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 04:40):

I'll have to dig into that in the future

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 04:40):

Do you think you could file a bug with the failing version

view this post on Zulip John Murray (Dec 16 2023 at 04:40):

Will do!

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 04:41):

Not sure when I will get to it, maybe monday. About to travel, so not exactly sure my free time, but I do want to figure out what is going on here.

view this post on Zulip John Murray (Dec 16 2023 at 04:43):

No rush! im unblocked for now so no worries.

view this post on Zulip John Murray (Dec 16 2023 at 04:50):

The generated glue has this as main

mainForHost : Task {} I32
mainForHost = main
pub fn mainForHost() -> roc_std::RocResult<(), i32> {
    extern "C" {
        fn roc__mainForHost_1_exposed_generic(_: *mut roc_std::RocResult<(), i32>, );
    }

    let mut ret = core::mem::MaybeUninit::uninit();

    unsafe {
        roc__mainForHost_1_exposed_generic(ret.as_mut_ptr(), );

        ret.assume_init()
    }
}

so i think uninit is only making the space for a roc result and not the actual effect which needs to be resolved?

view this post on Zulip John Murray (Dec 16 2023 at 04:54):

created https://github.com/roc-lang/roc/issues/6288

view this post on Zulip Luke Boswell (Dec 16 2023 at 05:05):

Another interesting place to look it basic-webserver.

view this post on Zulip Luke Boswell (Dec 16 2023 at 05:05):

It has a workaround to make glue generate the correct type for main

view this post on Zulip Luke Boswell (Dec 16 2023 at 05:06):

Here is the platform main which is modified so glue will generate correctly https://github.com/roc-lang/basic-webserver/blob/main/platform/main-glue.roc

view this post on Zulip Luke Boswell (Dec 16 2023 at 05:07):

This was a workaround Brendan provided, I am not sure how it works.

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 05:25):

Haha....now I remember. Thanks for posting that.

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 05:26):

The issue is that glue sees the task and assumes that the final value is being returned. So it doesnt generate code to call the task chain.

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 05:26):

By writing it explicitly as a function that returns a closure, we are actually specific the underlying closure type that the task truly generates

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 05:27):

That is why the workaround functions and what is need to make glue work here as well.

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 05:29):

For generating this glue we would need a workaround like rocMainForHost : {} -> ({} -> Result {} I32) or something like that for this example.

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 05:30):

Or it might just be:
rocMainForHost : ({} -> Result {} I32)

view this post on Zulip John Murray (Dec 16 2023 at 05:49):

Ahh that makes a lot of sense. Does the glue script not have enough introspection to know that task is opaque but it relies on effect?

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 07:09):

Yeah, it just doesn't have special handling for this currently

view this post on Zulip Brendan Hansknecht (Dec 16 2023 at 07:09):

Cause effect is a special type


Last updated: Jul 05 2025 at 12:14 UTC