-
-
Notifications
You must be signed in to change notification settings - Fork 340
Initial pass at new host ABI #7795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
47286b0
f53d97c
6664b2f
324c028
be5f68c
e528bbf
b7489e7
6cba47b
e5ffde4
abdbb99
609bac1
b231752
1448adb
d078548
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
/// All Roc functions that are exposed to the host take 1 argument and return void. | ||
/// The 1 argument is a pointer to one of these structs, which includes an address | ||
/// that the Roc call will write the return value into. This design makes Roc's ABI | ||
/// very simple; the calling convention is just "pass a single pointer". | ||
pub const RocCall = struct { | ||
/// Function pointers that the Roc program uses, e.g. alloc, dealloc, etc. | ||
ops: *RocOps, | ||
/// The argument that the Roc entrypoint function will receive from the host. | ||
/// (If multiple arguments are needed, this should be a pointer to a struct.) | ||
arg: *anyopaque, | ||
/// What the Roc entrypoint function will return to the host. | ||
/// (The caller should have allocated enough space for the Roc function to write | ||
/// the entire return value into this.) | ||
ret: *anyopaque, | ||
}; | ||
rtfeldman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/// The operations (in the form of function pointers) that a running Roc program | ||
/// needs the host to provide. | ||
/// | ||
/// This is used in both calls from actual hosts as well as evaluation of constants | ||
/// inside the Roc compiler itself. | ||
pub const RocOps = struct { | ||
rtfeldman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Like _aligned_malloc (size, alignment) - https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc | ||
/// Roc will automatically call roc_crashed if this returns null. | ||
roc_alloc: fn (*RocAlloc) void, | ||
/// Like _aligned_free - https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-free | ||
roc_dealloc: fn (*RocDealloc) void, | ||
/// Like _aligned_realloc (ptr, size, alignment) - https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-realloc | ||
/// Roc will automatically call roc_crashed if this returns null. | ||
roc_realloc: fn (*RocRealloc) void, | ||
/// Called when the Roc program crashes, e.g. due to integer overflow. | ||
/// It receives a pointer to a UTF-8 string, along with its length in bytes. | ||
/// This function must not return, because the Roc program assumes it will | ||
/// not continue to be executed after this function is called. | ||
roc_crashed: fn (*RocCrashed) void, | ||
/// Called when the Roc program has called `dbg` on something. | ||
roc_dbg: fn (*RocDbg) void, | ||
/// Called when the Roc program has run an `expect` which failed. | ||
roc_expect_failed: fn (*RocExpectFailed) void, | ||
}; | ||
|
||
/// When RocOps.roc_alloc gets called, it will be passed one of these. | ||
/// That function should write the allocated memory into `ret`. | ||
rtfeldman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// If it cannot proivde a non-null pointer (e.g. due to OOM), it | ||
/// must not return, and must instead do something along the lines | ||
/// of roc_crashed. | ||
pub const RocAlloc = struct { | ||
alignment: usize, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if this helps here since the struct has no padding, but in Zig std lib we've started using an Alignment type that stores the log2 value. It's pretty handy because you only need 6 bits to describe any alignment for a 64-bit address. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, that's a cool trick! I agree that it probably doesn't help here, but that's a good one to know for future reference. 🤘 |
||
length: usize, | ||
ret: *anyopaque, | ||
ops: *RocOps, | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a bit different from how we're doing it in the Rust compiler. Some notes:
|
||
|
||
/// When RocOps.roc_dealloc gets called, it will be passed one of these. | ||
/// (The length of the allocation cannot be provided, because it is | ||
/// not always known at runtime due to the way seamless slices work.) | ||
rtfeldman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub const RocDealloc = struct { | ||
alignment: usize, | ||
ptr: *anyopaque, | ||
ops: *RocOps, | ||
}; | ||
|
||
/// When RocOps.roc_realloc gets called, it will be passed one of these. | ||
/// That function should write the allocated memory into `ret`. | ||
/// If it cannot proivde a non-null pointer (e.g. due to OOM), it | ||
rtfeldman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// must not return, and must instead do something along the lines | ||
/// of roc_crashed. | ||
pub const RocRealloc = struct { | ||
rtfeldman marked this conversation as resolved.
Show resolved
Hide resolved
|
||
alignment: usize, | ||
new_length: usize, | ||
ret: *anyopaque, | ||
ops: *RocOps, | ||
}; | ||
|
||
/// The UTF-8 string message the host receives when a Roc program crashes | ||
/// (e.g. due to integer overflow). | ||
/// | ||
/// The pointer to the UTF-8 bytes is guaranteed to be non-null, | ||
/// but it is *not* guaranteed to be null-terminated. | ||
pub const RocCrashed = struct { | ||
utf8_bytes: *u8, | ||
len: usize, | ||
}; | ||
|
||
/// The information the host receives when a Roc program runs a `dbg`. | ||
/// | ||
/// The pointer to the UTF-8 bytes is guaranteed to be non-null, | ||
/// but it is *not* guaranteed to be null-terminated. | ||
pub const RocDbg = struct { | ||
// TODO make this be structured instead of just a string, so that | ||
// the host can format things more nicely (e.g. syntax highlighting). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I figure we can do the "structured |
||
utf8_bytes: *u8, | ||
len: usize, | ||
}; | ||
|
||
/// The information the host receives when a Roc program runs an inline `expect` | ||
/// which fails. | ||
/// | ||
/// The pointer to the UTF-8 bytes is guaranteed to be non-null, | ||
/// but it is *not* guaranteed to be null-terminated. | ||
pub const RocExpectFailed = struct { | ||
// TODO make this be structured instead of just a string, so that | ||
// the host can format things more nicely (e.g. syntax highlighting). | ||
utf8_bytes: *u8, | ||
len: usize, | ||
}; |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This
ops
pointer is what will actually get threaded through from one Roc function call to another behind the scenes.arg
andret
only get used in the outermost function wrapper that the host calls.This means that having all of these be pointers is a minimal cost, because all of them except for
ops
will only get dereferenced once per host call.