Hey all :wave: . Are there any non-rust (zig or c) alternatives to the cli-platform? And if not, is there anywhere I could read about creating one? I would like to do some advent of code problems but don't have a rust compiler setup on this machine.
Some platforms are zig based, but no equivalent of the cli platform. The benchmarks platform uses zig for example
Or maybe the easiest thing is to just embed the inputs into my .roc files? Does roc havemultiline strings?
I'm not sure about multiline strings (we should, just not sure on syntax/if it has been added). If you choose to embed, you should be able to just based a simple zig platform (like the platform switching one) and return and string of what to print.
Oh interesting. I'll take a look at the benchmarks platform, maybe learn from that. I saw the rocLovesZig hello world platform. Maybe the benchmarks platform has more to look at.
Otherwise, platforms don't really have good docs, though the API is significantly more stable than it use to be.
Are there any foreseeable roadblocks with using zig? Like anything rust-specific in the cli platform which I'll run into?
Roc does indeed have multiline strings! @Joshua Warner got them working recently
the syntax is triple quotes, and you can use them either on one line or across multiple lines
If you have questions, feel free to ask. Also, i am gonna move this chain of messages out of the day 9 thread, into it's own thread.
if you do it across multiple lines, the """
s each have to be on their own lines (with nothing else on the line) and indented the same amount
Are there any foreseeable roadblocks with using zig?
Nope. Zig should be just fine. The main thing is that you would be redefining the cli-platform/creating a new platform.
Yessss multiline strings! :praise:
If you wanted the exact same platform, you would have to conform to the same api. Otherwise, zig is powerful enough to make any platform.
Ok thanks for the input. Maybe I'll look into recreating the cli-platform in zig. Would be nice to have...
thats good to hear. i'm much more comfortable in zig than rust or c.
Go for it if you want. Would be interesting to see. Though if you just install rust, you shouldn't actually need to code in rust at all to use the cli platform to solve the advent of code problems.
oh and thanks for creating the new thread. i've now identified the 'new topic' menu item :eyes: and will consider using it.
Sharing API logic across differently-hosted platforms sounds like a fun future project...
In case host language preferences lead to increased platform fragmentation
Or maybe that's just a use case for extracting the common logic into a pure-Roc library for multiple implementation platforms to import
having one roc lib with multiple platforms supporting it sounds nice.
In case host language preferences lead to increased platform fragmentation
I think this is highly unlikely. Long term, most Roc developers are not likely to be platform devs.
@JanCVanB or anyone else have any ideas how to write this in zig?
// in examples/cli/cli-platform/src/lib.rs
pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr>
here's what i have in zig
const str = @import("str");
const RocStr = str.RocStr;
fn RocResult(comptime T: type) type {
return extern struct {
value: T,
is_error: bool,
};
}
pub export fn roc_fx_readFileBytes(roc_path: *RocStr) RocResult(RocStr)
but i'm getting this output:
$ roc 2021/1.roc --linker=legacy
๐จ Rebuilding platform...
ld: /tmp/roc_appYwTVxq.o: in function `roc_fx_fileReadBytes_fastcc_wrapper':
builtins-host:(.text+0xf2b6): undefined reference to `roc_fx_fileReadBytes'
I think you need: callconv(.C)
just noticed i was missing callconv(.C)
too but still getting the same error
I don't have time to debug tonight, but I can probably take a look tomorrow if you push it somewhere. Otherwise, I can ally attempt to answer questions.
You could try manual export with name: @export(roc_fx_getInt, .{ .name = "roc_fx_getInt" });
But for your function
That may work
Not sure though
manual export produces the same error. thanks. will let you know if i push code somewhere.
I wonder if your platform isn't recompiling for some reason.
i'm seeing zig error messages so it seems to be recompiling.
Can you compile and run a zig platform in the repo that has an effect?
Benchmarks or tui i think. Some thing with a host.zig
that has an FX function
yes i am able to run examples/cli/tui.roc
$ roc ../roc/examples/cli/tui.roc
๐จ Rebuilding platform...
Hello World!
i didn't realize tui was a zig platofrm. neat! will have a look there.
strange it looks like host.o contains the roc_fx_readFileBytes symbol. not sure what this means
$ nm platform/host.o | grep roc_fx
0000000000003ba0 T roc_fx_getInt
0000000000003d80 t roc_fx_getInt_help
0000000000003650 T roc_fx_putInt
00000000000038a0 T roc_fx_putLine
0000000000004030 T roc_fx_readFileBytes
i deleted all the artifacts in platform/ right before i ran this to verify i wasn't looking at an old object file
was getting a similar undefined reference to 'roc_fx_stdoutLine'
and fixed it with
// host.zig
comptime {
@export(roc_fx_putLine, .{ .name = "roc_fx_stdoutLine" });
}
so it seems to be something specific to roc_fx_readFileBytes
figured it out. this seems to be the required signature:
pub export fn roc_fx_fileReadBytes(roc_path: *RocStr, output: *RocResult) callconv(.C) void
oops i guess the args are switched. no wonder i couldn't print the string :smirk:
Also the name changed? roc_fx_readFileBytes
and roc_fx_fileReadBytes
?
omg you're right. i am blind :face_palm:
It's the simple things sometimes.
@Travis I didn't see it mentioned above so FYI we use Zig 0.9.1 in the Roc standard library so that might be the best version to use in your platform too.
here is a zig version of roc_fx_fileReadBytes()
pub export fn roc_fx_fileReadBytes(result: *RocResult, path: *RocStr) callconv(.C) void {
const file = std.fs.cwd().openFile(path.asSlice(), .{}) catch unreachable;
defer file.close();
const stat = file.stat() catch unreachable;
var buf = malloc(stat.size) orelse unreachable;
var slice = @ptrCast([*]u8, buf)[0..stat.size];
const amt = file.readAll(slice) catch unreachable;
std.debug.print("slice {s}\n", .{slice[0..10]});
std.debug.assert(amt == stat.size);
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), &result.bytes));
usizes[0] = 1; // Ok tag == 1
const outstr = @ptrCast(*RocStr, usizes + 1);
// const outstr = std.mem.bytesAsValue(RocStr, @ptrCast([*]u8, @alignCast(1, (usizes + 1)))[0..@sizeOf(RocStr)]);
outstr.* = .{ .str_bytes = slice.ptr, .str_len = slice.len, .str_capacity = slice.len };
}
it compiles and runs but doesn't seem to assign its result param correctly. i have verified that the incoming path is correct and that it can read the file's contents. however, nothing is printed from the roc program and subsequent prints in roc are broken after calling this function.
So i have a couple of questions:
roc_fx_fileReadBytes
? this one just calls malloc
.*RocResult
param.? i used the following definition i from crates/compiler/builtins/bitcode/src/utils.zig. but i'm not sure its correct or that i'm assigning to it correctly.pub const RocResult = extern struct {
bytes: ?[*]u8,
pub fn isOk(self: RocResult) bool {
// assumptions
//
// - the tag is the first field
// - the tag is usize bytes wide
// - Ok has tag_id 1, because Err < Ok
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), self.bytes));
return usizes[0] == 1;
}
pub fn isErr(self: RocResult) bool {
return !self.isOk();
}
};
Thanks @Brian Carroll yep thats the one i'm using:
$ zig version
0.9.1
i'm looking through crates/compiler/builtins/bitcode/src/str.zig and think i might be answering some of these questions
// allocate space for a (big or small) RocStr, but put nothing in it yet
pub fn allocate(length: usize, capacity: usize) RocStr {
Roc code itself uses roc_alloc
for heap allocations. Usually best to use that for anything that Roc code can touch.
It's often implemented as a wrapper around malloc anyway.
But Roc heap allocations also need reference counts. The standard library functions you found will do that for you.
another question: does this signature look correct? just noticed the rust version uses RocList<u8>
pub extern "C" fn roc_fx_fileReadBytes(roc_path: &RocList<u8>) -> RocResult<RocList<u8>, ReadErr> {
and that there is a zig version of RocList in crates/compiler/builtins/bitcode/src/list.zig
thanks @Brian Carroll i hadn't seen roc_alloc
yet. i'm thinking that RocStr.allocate
is the way to do this but still seeing no output in roc with it. is it correct to just read bytes from the file directly into the RocStr
? or is there an encoding i'm missing?
i assumed since this is readFileBytes
that it does no encoding
As long as zig uses utf8, reading into a RocStr
should be correct. I am actually surprised the rust version using a RocList<U8>
ok thats good to know. zig strings are not encoded in any way, just byte slices.
any idea about the correctness of the last few lines of my zig roc_fx_fileReadBytes()
here https://roc.zulipchat.com/#narrow/stream/347488-roctoberfest/topic/Alternative.20platform.20languages/near/303105460 ? i'm interpreting the first param as a *RocResult
and writing a 1 to the first usize
then treating the next 3 usize
as a RocStr
value.
here is an updated version which uses RocStr.allocate()
pub export fn roc_fx_fileReadBytes(result: *RocResult, path: *RocStr) callconv(.C) void {
const file = std.fs.cwd().openFile(path.asSlice(), .{}) catch unreachable;
defer file.close();
const stat = file.stat() catch unreachable;
var rocstr = RocStr.allocate(stat.size, stat.size);
var bytes = rocstr.str_bytes orelse unreachable;
const slice = bytes[0..stat.size];
const amt = file.readAll(slice) catch unreachable;
// std.debug.print("slice {s}\n", .{slice[0..10]});
std.debug.assert(amt == stat.size);
const usizes: [*]usize = @ptrCast([*]usize, @alignCast(@alignOf(usize), &result.bytes));
usizes[0] = 1; // Ok tag == 1
const outstr = @ptrCast(*RocStr, usizes + 1);
outstr.* = rocstr;
}
RocResults
doesn't work that way. The tag is at the end.
I am pretty sure we always put tags at the end to save space by making them able to pack smaller
Also, I think you should be returning the RocResult, not taking it as an out param.
Brendan Hansknecht said:
Also, I think you should be returning the RocResult, not taking it as an out param.
if this is the case, any idea what the first param is? path is being correctly passed as the second param and i'm able to print it out.
Ah, that's just c-abi fun. I guess technically under c-abi the two function signatures are the same.
Anything returned that is larger than 128bit is converted to return via pointer. But zigs callconv c should handle that for you. Also, zig handling it means you don't have to think about the differences as you change platforms.
ah that makes sense. so would it be proper to ignore the first param and return a RocResult by value or go back to the out param?
You would have to go back to the out param.
Also, as a base test, maybe make it just return a RocStr and not a result. That will ensure the pipelining before you figure out the RocResult correctly.
hmm ok thanks. will give it a try.
Hmm. Also just double checked some code. How you set the roc result originally actually looks correct. We do put the tag at the beginning. I stand corrected.
Hmmm....i am confused. In our rust code for roc result, the tag is at the end. In a util function in zig, the tag is at the beginning.....definitely a bug somewhere.
Ah, the zig function is never used....so probably wrong.
yeah i was a little confused by that too. are you looking at const RocResult =
in crates/compiler/builtins/bitcode/src/utils.zig ?
Ok, yeah sorry for the confusion. Ignore the code in utils.zig
it isn't used and is outdated and wrong. So I am pretty sure the tag is at the beginning and you should define RocResult like this (rust definition):
#[repr(C)]
pub struct RocResult<T, E> {
payload: RocResultPayload<T, E>,
tag: RocResultTag,
}
#[repr(u8)]
#[derive(Clone, Copy)]
enum RocResultTag {
RocErr = 0,
RocOk = 1,
}
#[repr(C)]
union RocResultPayload<T, E> {
ok: ManuallyDrop<T>,
err: ManuallyDrop<E>,
}
ok sounds good. one last question, would the result payload be a *RocStr
or RocStr
?
or more generally *T
vs T
Should be a RocStr
So T
i made a repo for this incase anyone is interested in using or contributing
https://github.com/travisstaloch/roc-zig-cli-platform
so far it can only really do Stdout.line
. the following works with the commented out lines currently broken.
main : Program
main =
pathstr = "platform-test/1.txt"
path = Path.fromStr pathstr
task =
_ <- Task.await (Stdout.line (Path.display path))
_ <- Task.await (Stdout.line "this gets printed")
# contents <- Task.await (File.readBytes path)
# _ <- Task.await (Stdout.line ((Str.fromUtf8 contents) # silently fails, breaks subsequent prints
# |> (Result.withDefault "oops")))
Stdout.line "this doesn't get printed"
Program.quick task
its a straight copy of the rust platform in examples/cli/cli-platform
$ ./build.sh
๐จ Rebuilding platform...
platform-test/1.txt
this gets printed
this doesn't get printed
i am hoping to get some guidance on how to make File.readBytes
work correctly. here is my attempt: https://github.com/travisstaloch/roc-zig-cli-platform/blob/main/platform/host.zig#L258
Just a general note for you, there is a tool roc glue
that will make this significantly easier in the future. Currently it only supports rust, but it is made to take roc data structures and convert them to another language. Would eventually also do function signatures. That would more or less directly solve the main problems you are fighting here.
neat! will keep an eye on it. i'm guessing it depends on a rust/cargo? that might explain the panic when i try to use it
Brendan Hansknecht said:
Also, as a base test, maybe make it just return a RocStr and not a result. That will ensure the pipelining before you figure out the RocResult correctly.
I forgot to mention that I did attempt this, and was glad for the suggestion. but I couldn't manage to create correct roc Effect code to call it.
and if you have any suggestions on how to do it, i'd like to try them
on a different note, I wonder if the issues i'm having could possibly be invoking some zig/c abi bugs? there was discussion in the zig discord yesterday about this https://discord.com/channels/605571803288698900/785499283368706060/1028831580426354718 - sounds like its specific to zig extern structs as arguments to c functions
i might try implementing roc_fx_fileReadBytes()
in c
and if that works, i wonder if its possible to have a mixed c/zig host?
Zig will compile c, do that should be fine
Also, I'll try and take a look today to see if I missed something with the zig signature
Zig will compile c, do that should be fine
are you thinking of @cImport or does roc automatically add host.c files to the compilation?
cause @cImport is really meant for headers, not implementations
I just mean that I know zig can be used to build projects that are a mix of c and zig. So this should be possible to set up
oic. yeah would be nice
We would probably have to change roc to support build.zig files, but should be doable.
do you happen to know much about how platforms are built? i want to know why its possible to @import("str") package in a host.zig
the only build.zig i could find in the whole project is ./crates/compiler/builtins/bitcode/build.zig and it doesn't seem to call addPackage()
anywhere
Just a simple hack were we call zig build-exe with an extra age for our internal str.zig file. Look at crstes/compiler/build/src/link.rs (full disclosure: very hacked together and naturally grown)
maybe there is a command line for it ie: $ zig build-exe ... --pkg-begin str .. --pkg-end
ah ok thank you
And yeah, exactly what you just posted
Realistically, we shouldn't do that. Our str.zig file should be internal only. Platforms longer term should depend on glue for a proper definition. Just doesn't support anything but rust yet.
good to know where that happens
Ok. So example implementation that at least mostly works:
pub export fn roc_fx_fileReadBytes(path: *RocList) callconv(.C) ResultRocList {
std.debug.print("Called fileReadBytes\n", .{});
if (path.bytes) |path_ptr| {
const path_slice = path_ptr[0..path.len()];
var realpathbuf: [256]u8 = undefined;
const realpath = std.fs.cwd().realpath(".", &realpathbuf);
std.debug.print("path '{s}' realpath {s}\n", .{ path_slice, realpath });
const file = std.fs.cwd().openFile(path_slice, .{}) catch |e|
roc_panic_print("{s} file path '{s}'\n", .{ @errorName(e), path_slice });
defer file.close();
const stat = file.stat() catch |e|
roc_panic_print("{s} file path '{s}'\n", .{ @errorName(e), path_slice });
// std.debug.print("stat.size {}\n", .{stat.size});
var roclist = RocList.allocate(@alignOf(usize), stat.size, stat.size);
if (roclist.bytes) |bytes| {
const slice = bytes[0..stat.size];
const amt = file.readAll(slice) catch unreachable;
std.debug.assert(amt == stat.size);
return ResultRocList{
.payload = roclist,
.tag = 1,
};
}
}
return ResultRocList{
.payload = RocList.empty(),
.tag = 0,
};
}
Note, using RocList to match the Effect that is defined: fileReadBytes : List U8 -> Effect (Result (List U8) InternalFile.ReadErr)
For your roc main:
_ <- Task.await (Stdout.line (Path.display path))
_ <- Task.await (Stdout.line "this gets printed")
contents <- Task.await (File.readBytes path)
_ <- Task.await (Stdout.line ((Str.fromUtf8 contents) # silently fails, breaks subsequent prints
|> (Result.withDefault "oops")))
Stdout.line "this doesn't get printed"
The last line doesn't print because the Task.await
is returning a failure. That kills the rest of the task chain.
Why does the function return a failure even when you set the tag to 1:
Try commenting out Unrecognized I32 Str
in InternalFile.roc
. Now everything should run fine.
oh wow! i'm going to try it out now :pray:
The root issues is that the Error case is larger than the Ok case. As such, your result is not the right size. The payload is not the size of a RocList. It is the size of a tag union that contains a RocStr and an I32.
Commenting out that line makes the payload smaller than a RocList and everything works.
that makes a lot of sense about the await chain being broken. i wondered about that.
i am surprised this works. i was never able to use path as the first param. maybe a c-ism like you talked about yesterday (returning a value vs out param)
The root issues is that the Error case is larger than the Ok case. As such, your result is not the right size. The payload is not the size of a RocList. It is the size of a tag union that contains a RocStr and an I32.
are you referencing my impl or yours here?
That was a follow up to this line:
Try commenting out
Unrecognized I32 Str
inInternalFile.roc
. Now everything should run fine.
The defined error type in Roc.
InternalFile.ReadErr
Also, I didn't fix the issue in the zig code I posted.
Which is why I just said comment out that line for now.
Oh, just noticed.
This is wrong: var roclist = RocList.allocate(@alignOf(usize), stat.size, stat.size);
The last argument should be the element size, so @sizeOf(u8)
i think i've made all the changes you indicated. but i'm not seeing any file output. did you see the file printed out?
$ ./build.sh
๐จ Rebuilding platform...
platform-test/1.txt
this gets printed
Called fileReadBytes
path 'platform-test/1.txt' realpath ~/roc/advent-of-code
roclist.bytes != null # added by me
runtime: 0.081ms
The last argument should be the element size, so @sizeOf(u8)
still no file output. are you seeing any thing different?
Yeah, I saw the file printed out.
Can you push your changes so I can take a look?
yeah sure. just a minute. btw, what os are you on? im on linux debian x64
aw snap nevermind. i edited the wrong InternalFile.roc (i have a sub folder of the originals)
it works! :tada:
was about to commit the changes and noticed the missing diff
I generally use an M1 mac (light weight and portable form factor), but I also use debian x64 pretty often.
ok only reason i asked is i thought it might account for the differences. but no just edited the wrong file. :laughing:
but thanks a lot! i'll have to think about this a bit maybe have some questions later on.
do you want to make a pr or should i just credit you in comments?
ok i think i'm following what you said about the payload sizes differing and how commenting that line out make the sizes comptible now.
so the fix would be to implement the <T, E> union like in rust?
like this:
#[repr(C)]
union RocResultPayload<T, E> {
ok: ManuallyDrop<T>,
err: ManuallyDrop<E>,
}
but then i wouldn't know how to get the size of E :thinking:
do you want to make a pr or should i just credit you in comments?
Don't worry about it. Just here to help
so the fix would be to implement the <T, E> union like in rust?
Correct
but then i wouldn't know how to get the size of E :thinking:
Yeah....this is where glue would be quit useful. Instead you have to do it manually.
There error type should be fine to define as:
code: i32,
message: RocStr,
tag: u8
Where tag picks the specific ReadError
in alphabetical order and code/message are only set when using the Unrecognized
variant.
You should be able to make constructors such that the use is less painful.
oic so i would need to sync the sizes myself. makes sense. idk why i was thinking it could be provided dynamically somehow at compile time.
i can see where glue would be nice to have for this.
Yep, it makes platform dev a world nicer
hey @Brendan Hansknecht last night i tried running other's aoc roc solutions including yours. as a result i added Stdout.line
support locally and then ran into foc_fx_args
missing. do you have any recommendations on how implement that one?
I think it just returns a List (Str)
of the args passed into the program.
i tried to wing it by just making that function and building a RocList<RocString> from std.process.args
but there were 0 args when i tried to run it which was strange.
that seems wrong, there should always be at least the executable name...
hmm
Also, zero args on the zig side, or none got passed to the roc app?
i looked at your #3990 pr a bit but didn't get too far. i see there are roc_main
and roc__mainForHost_1_exposed_generic
functions but i'm not sure if those are important here.
The rust version just defines it as:
#[no_mangle]
pub extern "C" fn roc_fx_args() -> RocList<RocStr> {
// TODO: can we be more efficient about reusing the String's memory for RocStr?
std::env::args_os()
.map(|os_str| RocStr::from(os_str.to_string_lossy().borrow()))
.collect()
}
So basically what you said.
zero args. i tried using both process.args()
and process.argsAlloc()
and both had 0 args
yeah i looked at the rust version too and was following it. thats why i assumed i could just do what i did. is there something else that has to happen first?
Also, 0 args when printing them from zig? If so, that is probably some sort of zig issue.
If you can print out the args from zig, probably just some sort of issue generating the list of strings.
Also, just creating that effect should be enough to my understanding.
ooh thats one thing i didn't consider much. i'll review the .roc files in my platform folder. might have commented something out or forgot to copy over.
i don't see anything amiss with the .roc files. i see Effect.roc/args and Process.roc/withArgs were copied over from the cli-platform.
maybe i'll see whats in std.os.argv
yeah strange. here's what i see after building
$ day1/exec-part1 day1/data.txt
argv.len 0 # printed out from zig
Expected a file name passed on the command line
i'll search for that error message 'Expected a file name passed on the command line' see what that turns up
ah that message was from your advent day1 solution. :laughing: of course. so something is not happening on zig side. i wonder why there are no args there :thinking:
I guess make a zig app without any roc and make sure it works correctly on your computer?
yeah args look fine from standalone zig app:
$ cat test.zig
const std = @import("std");
pub fn main() !void {
const allocator = std.heap.page_allocator;
const args = try std.process.argsAlloc(allocator);
std.debug.print("args.len {}\n", .{args.len});
}
travis:/tmp/roc-aoc-2021
$ zig run test.zig -- asdf asdf
args.len 3
Interesting. I wonder if it has to be run from main/roc is messing that up? What happens if you put the function in main instead of in roc_fx_args?
you mean what happens if i print out args from host.zig/main()? i tried that too last night and saw 0 args
Can you try with --linker=legacy
same thing. no args. here's how i called it
$ roc day1/part1.roc --linker=legacy -- day1/data.txt
Hmm....i am quite confused.
yeah me too. i'm looking at some of the rust platform examples like the false interpreter have an args param
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(output: *mut u8, args: &RocStr);
while others have none
#[link_name = "roc__mainForHost_1_exposed_generic"]
fn roc_main(output: *mut u8);
Just different designs. False interpretter passes it in as an arg to main.
The cli platform passes it in via an effect.
if you have time to try running it, i just pushed a wip commit with debug statements in host.zig/main() and roc_fx_args()
run against your day1/part1.roc should trigger the debug statements
here's what i see
$ roc day1/part1.roc -- day1/data.txt
๐จ Rebuilding platform...
main() args.len 0
roc_fx_args() args.len 0 osargs.len 0
Expected a file name passed on the command line
runtime: 0.030ms
So my only guess is the fact that we tell zig to build an object file. Then call ld directly to link it is somehow breaking things.
So this may be a case of quite annoying to debug
Even letting zig control the building and linking doesn't work: zig build-exe platform/host.zig ~/Projects/roc-aoc-2021/day1/exec-part1.o
It runs, but no args.
I figured out the issue. It makes little sense to me, but I found it.
pub export fn main() callconv(.C) u8
-> pub fn main() u8
Causes the program the have args and then crash for what look like proper reasons.
Something with list.listAppendUnsafe
thanks so much! :heart: i never would have thought of this. i'm seeing a similar crash here now but the length of the args looks correct. :+1:
got it working. fix was passing @ptrCast([*]u8, &rocstr)
instead of rocstr.str_bytes
in the meantime, i've got Env.cwd working.
and Env.var
somehow Env.setCwd doesn't seem to work tho.
atleast when i try to cd /tmp
and then print cwd
again it doesn't change
and its also seems wierd that arg 0 is ' /proc/self/fd/3' :thinking:
$ foo=bar roc platform-test/main.roc -- asdf asfd
๐จ Rebuilding platform...
platform-test/1.txt
...
cli arg 0: /proc/self/fd/3
cli arg 1: asdf
cwd ~/roc/advent-of-code
cwd2 should be /tmp ~/roc/advent-of-code
env:foo=bar
runtime: 0.109ms
arg 1 looks fine. exe path is wierd. i'll try making roc_fx_exePath
next and see if that is different.
Arg 0 is weird because we compile in memory and then launch from the roc run executable as opposed to launching the binary from disk
aaah. i see. good to know
ok makes more sense now. arg 0 is only borked when run with roc file.roc
but runing the compiled version arg 0 looks fine.
oops. Env.setCwd works fine. i was printing out the same path twice :face_palm: :laughter_tears:
Awesome to see all the progress
thanks :big_smile: . you deserve a lot of the credit. been very helpful and patient with my flails.
:pray:
@Brendan Hansknecht if you have a chance, take a look at my error handling attempts: https://github.com/travisstaloch/roc-cli-platform-zig/blob/main/platform/host.zig#L249 I haven't really tested the error handling except to verify that it can catch "Not Found".
but i reworked as a union <T,E> like we discussed. not 100% sure about the layout.
but it atleast works after adding back the ReadError Unrecognized I32 Str,
line
and doesn't panic anymore. the error handling was tricky. only real changes are to roc_fx_fileReadBytes
its strange that zig's Dir.openFile() wasn't returning an error for file not found. and then there was a panic in the File.stat() call which meant i had to check for fd == -1
wonder if this has changed since 0.9.1
I think Payload
needs to be sorted alphabetically, otherwise, I think that is correct. Would need to do some testing to confirm though.
Should have time to do that later
Also, really interesting helper for initializing them.
yeah i thought the helper makes verbose union init syntax a lot cleaner
if you have any ideas for testing i'm all ears. i was considering trying to pare things down into separate files.
i wonder if its possible to run a Task in an expect? i tried the other day but couldn't figure it out.
Travis said:
i wonder if its possible to run a Task in an expect? i tried the other day but couldn't figure it out.
not yet! we're going to have a separate expect-fx
keyword for that, but it doesn't exist yet :big_smile:
well, that's for testing in the sense of actually running the task - there's a separate concept of "simulating" tasks where you never actually run them, but rather sort of mock out their inputs and outputs; that's a separate topic
ooh expect-fx
sounds neat. cool to hear thats in the works. and there was good reason it wasn't working for me.
The manual way to test is to trigger each error from roc and then have roc print a message for each error. Should tell you if it gets every value correctly.
yeah i think i'll try to break up into smaller tests. now that cli args are working, will be a bit easier to make the errors happen. and just letting the program fail sounds good.
btw, i tried to repro the wierd zig 0.9.1 behavor i was describing earlier using on latest download build and wasn't able to do so.
not sure if this is a good repro or not.
$ zig version
0.10.0-dev.4280+c3d67c5c4
$ cat /tmp/test.zig
const std = @import("std");
export fn func() callconv(.C) isize {
const fname = "/tmp/test.zigg";
std.debug.print("1\n", .{});
const f = std.fs.cwd().openFile(fname, .{}) catch {
return -1;
};
std.debug.print("2\n", .{});
defer f.close();
std.debug.print("3\n", .{});
const s = f.stat() catch {
return -1;
};
std.debug.print("stat.size {}\n", .{s.size});
return 0;
}
pub export fn main() callconv(.C) void {
const x = func();
std.debug.print("x {}\n", .{x});
}
$ zig run /tmp/test.zig
1
x -1
same result with a normal fn main
I thought the issue was only with std.process.args/argsAlloc
which you aren't calling in that example.
Or is this for a different issue?
a different issue. i couldn't figure out why zig's Dir.openFile
and File.stat
weren't returning errors and early returning in roc_fx_fileReadBytes
's catches. thats where the funky error handling is required (checking if fd == 1).
what was actually happening is the underlying call to stat was panicking when that should have been caught
and finallly after i figured out what was going on (with wierd error traces), the defer file.close()
was also panicking. :cry:
so that had to be move down a line. i don't remember 0.9.1 having these issues. something seems off.
my hunch is some abi issues but its just a guess
ah, ok
hey @Brendan Hansknecht if you have any time could you look at this branch? https://github.com/travisstaloch/roc-cli-platform-zig/tree/detect-read-errs
Can probably do so sometime later in the day. Anything specific you want me to look at?
awesome. yeah: i feel like the host.zig is correct but for some reason i can't catch the errors https://github.com/travisstaloch/roc-cli-platform-zig/blob/detect-read-errs/test/file-read-errors.roc. does the error matching here look right to you ?
i've simplified host.zig too. seems closer to what what you indicated before. ReadErr is now just a struct
code: i32,
message: RocStr,
tag: u8,
this is a branch where i've only changed that ^ file and host.zig.
https://github.com/travisstaloch/roc-cli-platform-zig/blob/detect-read-errs/platform/host.zig#L254
my hunch is that the roc error catching is the issue.
because when i remove when
's else
branch, there is a big error message suggesting to me that the ReadErr types are wrong.
even though the match is supposed to be exhaustive
ok i think i got the right error matching syntax now.
and it seems to be working.
$ roc test/file-read-errors.roc
๐จ Rebuilding platform...
not found
:duck:
hunch was right anyway. i was just struggling matching the error. :joy:
other errors look good now too. except for Unrecognized.
when i try to return one of those i get a segfault. :thinking:
i pushed another commit. for now it returns an Unrecoginzed
and segfaults when file path is 'foo'
running this will trigger it:
$ roc test/file-read-errors.roc
i've got a solution although i'm not sure its a good idea
this layout works with message
and code
now swapped from the way i had them. and added the _padding
field
pub const ReadErr = extern struct {
message: RocStr,
code: i32,
_padding: [7]u8 = undefined,
tag: u8,
$ roc test/file-read-errors.roc
๐จ Rebuilding platform...
Unrecognized code 69 message fo ur twenty
other tests look good too. i'll push another commit now.
So I was looking at the rust datastructures generated by glue and trying to figure out what the correct generation is when I realized that glue definitely has a bug in generation. I am going to take a look at that and them hopefully get back to you on what the correct representation actually is.
Ok. So I looked into this a bit more and realized what I though was a bug was actually me looking at the 32bit definition instead of the 64bit definition.
If I am reading the code correctly, the definition should be:
pub const ReadErr = extern struct {
code: i32,
message: RocStr,
tag: u8,
_padding: [7]u8 = undefined,
This leads to 4 bytes for the i32, 4 bytes of padding, 24 bytes for the RocStr, then the tag as a u8. So 40 bytes total with the tag at index 32.
ah. hmm ok. well that seems to be correct. not sure how i was getting something that looked correct earlier. but this is good.
thank you :+1:
in the last little big ive been working on a strictly testing/debugging version of roc_fx_fileReadBytes
which just returns errors.
wasn't working til now. so this seems correct.
glad it works.
ooh wait. think i spoke too soon again. Unrecognized
doesn't seem to work but the others look good.
thats the one with the code/message
the tag looks fine but the code and message are wrong.
haha shoot i think this is my mistake. nevermind for now.
yeah i had Unrecognized/Unsupported mixed up :blushing:
and it segfaulted. i had to flip code/message back to the say i had it. this is what seems to work.
pub const ReadErr = extern struct {
message: RocStr,
code: i32,
tag: u8,
_padding: [7]u8 = undefined,
let me make a better test script so i can be sure i'm correct.
the reason i thought this was correct in the first place is this: line 182 in file_glue.rs
#[cfg(any(
target_arch = "aarch64",
target_arch = "x86_64"
))]
#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
#[repr(C)]
struct ReadErr_Unrecognized {
pub f1: roc_std::RocStr,
pub f0: i32,
}
the one i'm looking at is examples/cli/cli-platform/src/file_glue.rs
*edited added arches
and on line 78 theyre backwards for 32 bit
#[cfg(any(
target_arch = "arm",
target_arch = "wasm32",
target_arch = "x86"
))]
#[derive(Clone, Debug, Default, Eq, Ord, Hash, PartialEq, PartialOrd)]
#[repr(C)]
struct ReadErr_Unrecognized {
pub f0: i32,
pub f1: roc_std::RocStr,
}
oh, duh
My bad
We first sort fields by alignment and then by name
So on a 64bit system, Str before I32.
Cause Str contains pointers and is aligned to 8
On the 32 bit system, they are both aligned to 4.
I didn't realize that this optimization also applied to tag unions, I thought it was just for records.
ah good. that makes a lot of sense now.
i'm just going to shoot for a working 64 bit impl before thinking about 32
but glad to know a little more about these types of details. :smile:
Yeah, this is a big reason for glue existing in general.
i've got a good test script now passing in error names from the cli. i was experimenting trying to get them all corrected and i this is what i've come up with.
pub const ReadErr = extern struct {
message: RocStr,
code: i32,
tag: usize,
with this all the error tags are good and the Unrecognized
payload is too. :big_smile:
Yeah, should work on any 64 bit little endian system.
do we need big endian support too?
not yet...maybe one day
currently the only supported platforms are little endian and either 32 or 64 bit
ok good to know.
Last updated: Jul 06 2025 at 12:14 UTC