rokdocs
v0.1

8.5. Extern "C" symbols

Defines the signatures of the symbols exposed by librokr.a. Compiler-generated code and the standard library call these symbols.

On the rok side they are declared with the runtime ABI extern "rok" (5.9.2), which the toolchain places in rok::core::rt as pub(pkg). In the blocks below a fn value is RokFn (8.5.0a) and all other values are as in the C ABI. str is struct { const uint8_t *ptr; size_t len; }; runtime functions taking or returning str follow the platform C ABI for a 16-byte struct (8.6.3), like RokFn: by value in registers where the ABI allows, never split between registers and the stack, and by reference on Windows x64. A program may declare a runtime symbol itself (extern "rok" / extern "C"); its declaration must have the signature given here, and a different one is an error.

Important

Every symbol starts with the rok_ prefix, to avoid symbol collisions with external libraries and user code.

8.5.0. Thread-safety classes

Each symbol definition carries a Thread-safety annotation. It is part of the contract between compiler-emitted code and the runtime and states in which calling contexts the symbol may be called safely. There are six classes.

ClassMeaning
thread-safeSafe to call concurrently from multiple threads (the runtime synchronizes internally)
caller-thread-onlyBound to the calling thread's context - another thread cannot act on behalf of that caller
process-initExactly once at process start, before entering mt
process-shutdownExactly once at process exit, after mt ends
per-thread-initOnce at the start of each worker thread, by that thread for itself
per-thread-shutdownOnce at the end of each worker thread, by that thread for itself

caller-thread-only means the function's effects are tied to the caller's TLS slots or stack frame, so another thread cannot act on behalf of that caller. It does not mean the function is non-reentrant; multiple threads may call it concurrently, each for itself.

8.5.0a. RokFn - C ABI form of function values

The C representation used when passing a rok function value {code, env} (4.4) to a runtime symbol.

typedef struct {
    void *code;   // code pointer that takes env as its first argument
    void *env;    // capture environment (NULL for named functions)
} RokFn;

RokFn is passed by value as a C struct. The actual register assignment follows the target C ABI's rules for 16-byte structs.

8.5.1. GC area

rok_gc_alloc_slow

The TLAB slow path from Allocation. Thread-safety: thread-safe.

extern "rok" {
    fn rok_gc_alloc_slow(size: usize, align: usize, type_id: u32) -> GcBuf<u8>;
}

The arguments and return value mean:

  • size: the total object size: the 16-byte header plus the payload, aligned.
  • align: the object's alignment requirement, usually 8, larger when #[repr(align(N))] is specified.
  • type_id: the object's type ID.
  • Returns: a GC reference (GcBuf<u8>, the runtime ABI's GC pointer) to the object, starting at the header; the function fills in type_id and gc_flags.

It never returns null. Out of memory is fatal (rok_oom), and a request above isize::MAX is the fatal error "allocation too large" (exit 101).

rok_alloc_raw / rok_realloc_raw

Allocates raw memory outside the GC (stdlib 1.4 alloc). Thread-safety: thread-safe.

extern "rok" {
    fn rok_alloc_raw(size: usize, align: usize, zeroed: i32) -> usize;
    fn rok_realloc_raw(ptr: usize, old_size: usize, align: usize, new_size: usize) -> usize;
}

If zeroed is nonzero, the allocated memory is zero-filled. rok_alloc_raw returns null (0) if size is 0, if align is not a power of two, or if allocation fails; it does not abort. rok_realloc_raw also returns null on failure. Failure handling is the caller's (rok::bare::alloc) responsibility.

rok_gc_wb_slow

The write barrier slow path from Barriers and root tracking. Thread-safety: caller-thread-only (records into the wb log buffer in TLS).

extern "rok" {
    fn rok_gc_wb_slow(obj: Ptr<u8>);
}

The argument obj is a pointer to the object containing the modified slot; there is no return value. Effect: adds obj to the remembered set and flushes when the log is full.

rok_gc_register_type

Registers a type's ScanTable. Thread-safety: process-init.

extern "rok" {
    fn rok_gc_register_type(type_id: u32, scan_table: ConstPtr<u8>);
}

Called at process start, before entering mt, so no synchronization is needed.

rok_gc_pin / rok_gc_unpin

Pinning of FFI objects (see Object layout and memory spaces and extern blocks (FFI declarations)). Thread-safety: thread-safe.

extern "rok" {
    fn rok_gc_pin(obj: Ptr<u8>) -> Ptr<u8>;       // returns the pinned object (same address)
    fn rok_gc_unpin(obj: Ptr<u8>);
}

A GC pointer passed to an extern "C" function or a blocking runtime call is not moved even without pinning, because the argument stays in a root until the call ends (7.4.1). Pinning is needed only when C code keeps the pointer after the call. Pin sets PINNED on the object, excluding it from evacuation and keeping it alive; unpin releases it.

rok_gc_collect

An explicit GC trigger, rarely used. Thread-safety: thread-safe.

extern "rok" {
    fn rok_gc_collect();
}

Performs a full collection and returns after it finishes.

rok_gc_secret_new / rok_gc_secret_wipe

Secret storage for rok::core::crypto::Secret (stdlib 2.21.5). Thread-safety: thread-safe.

extern "rok" {
    fn rok_gc_secret_new(len: usize) -> GcBuf<u8>;
    fn rok_gc_secret_wipe(obj: GcBuf<u8>);
}

rok_gc_secret_new allocates a LOS object of type id 1 (no references) with WIPE set (8.1.3). Its zeroed payload holds len bytes rounded up to a multiple of 8. It may collect first, and a request above isize::MAX is the fatal error "allocation too large". rok_gc_secret_wipe overwrites the whole payload with volatile stores followed by a compiler fence; it neither allocates nor blocks, so it is no safepoint (8.4). The collector overwrites the payload the same way before it frees the object.

8.5.2. Runtime area

rok_runtime_init

Initializes the runtime. Thread-safety: process-init.

extern "rok" {
    fn rok_runtime_init();
}

Called right after process start, before main(). Effects: creates the worker pool, mmaps the GC spaces and initializes the I/O driver. The compiler-generated C-ABI main calls rok.register_types right after this call (8.2.13).

The runtime additionally exports the thread-local block rok_tls (8.3.8).

rok_runtime_shutdown

Shuts down the runtime. Thread-safety: process-shutdown.

extern "rok" {
    fn rok_runtime_shutdown();
}

Called right after main() ends. Every other rok thread stops for good at its next safepoint, after finishing any runtime or C code it is in. Then the worker pool shuts down and outstanding resources are released.

rok_exit

Terminates the process immediately. Thread-safety: thread-safe.

extern "rok" {
    fn rok_exit(code: i32) -> !;
}

rok::std::env::exit calls this symbol. finally and Drop are not run (4.16.4).

String helpers

Internal helpers backing FromStr and str::bytes (stdlib 2.15). Thread-safety: thread-safe. str arguments use the struct form of the preamble.

extern "rok" {
    fn rok_str_eq(a: str, b: str) -> i32;                            // 1 if equal, else 0
    fn rok_str_byte(s: str, i: usize) -> u8;                         // i-th byte (bounds check is the caller's)
    fn rok_str_slice(s: str, start: usize, end: usize, at: str) -> str;  // s[start:end]. panics at location at if the range / UTF-8 boundary is wrong
    fn rok_str_parse_int(s: str, radix: u32, signed: i32, bits: u32, out: GcBuf<i64>) -> i32;
                                        // 0 ok, 1 empty, 2 invalid digit, 3 out of range; value bits in out[0]
    fn rok_str_parse_float(s: str, out: GcBuf<f64>) -> i32;           // 0 ok, 2 invalid, 3 out of range (finite digits past the type's range); value in out[0]
}

rok_runtime_yield

The yield function of the Safepoint protocol, i.e. the slow path of the safepoint poll. Thread-safety: caller-thread-only (called by the calling mutator as a result of polling its own yield_flag).

extern "rok" {
    fn rok_runtime_yield();
}

rok_enter_native / rok_leave_native

Slow paths of the native bracket (8.4.10). Thread-safety: caller-thread-only.

extern "rok" {
    fn rok_enter_native();                // entering C: this thread counts as PARKED
    fn rok_leave_native();                // back from C: wait out a running collection
}

The compiler inlines the fast path around every extern "C" call: a seq_cst store of PARKED / RUNNING to the mutator state, then a check of the yield flag. Only when the flag is set does it call these.

rok_native_reenter / rok_native_resume

The entry and exit of every extern "C" fn definition, so that a C callback runs as a mutator. Thread-safety: caller-thread-only.

extern "rok" {
    fn rok_native_reenter() -> u32;       // if the thread was PARKED (inside C), leave native; returns whether it was
    fn rok_native_resume(parked: u32);    // re-enter native if reenter left it
}

rok_thread_init / rok_thread_shutdown

Worker thread lifecycle. Thread-safety: rok_thread_init is per-thread-init, rok_thread_shutdown is per-thread-shutdown.

extern "rok" {
    fn rok_thread_init();
    fn rok_thread_shutdown();
}

Called at the start and end of each worker thread to initialize or tear down the TLS of the TLS slots.

Internal symbol families

These families are runtime-internal helpers of the reference runtime and are not part of the ABI contract: rok_fmt_*, rok_str_* helpers, rok_char_*, rok_nat_*, rok_stdio_*, rok_time_*, rok_gc_stats_*, rok_buf_*, rok_cstr_len.

Functions that are not safepoints

On the rok side they are declared in an extern "rok" block marked #[no_safepoint] (5.9.2).

The following runtime functions are guaranteed never to allocate and never to block, so a call to them is not a safepoint (8.4.2, 8.9.12): rok_str_len, rok_str_byte, rok_str_cmp, rok_str_eq, rok_str_eq_at, rok_str_find, rok_str_rfind, rok_str_char_count, rok_str_hash, rok_str_bytes_buf, rok_buf_copy, the rok_char_* property and case functions, rok_fmt_len, rok_f64_*, rok_f32_*.

8.5.3. Coroutine area

rok_coro_frame_alloc

Allocates a coroutine frame. Thread-safety: thread-safe.

extern "rok" {
    fn rok_coro_frame_alloc(size: usize, type_id: u32) -> GcBuf<u8>;
}

Nearly identical to rok_gc_alloc_slow, but allows frame-specific handling.

rok_coro_park / rok_coro_unpark

The compiler does not emit these two symbols (await uses the parking registry of rok::core::task). The runtime provides them for C and low-level code. The address of frame (any GC object) is the parking key, and rok_coro_unpark(frame) wakes every waker parked on that key. Calling them outside a task executor aborts.

Thread-safety: rok_coro_park is caller-thread-only (parks the calling coroutine itself), rok_coro_unpark is thread-safe (may be called from another thread).

extern "rok" {
    fn rok_coro_park(frame: Ptr<u8>, waker: Ptr<u8>);
    fn rok_coro_unpark(frame: Ptr<u8>);
}

rok_coro_park suspends the coroutine and registers the waker. rok_coro_unpark resumes the coroutine by pushing it onto the task queue.

Here waker is an opaque pointer (Ptr<u8>) to a bundle of wake function and context, i.e. the runtime's Waker object (2.20.2). The internal representation of Waker is not exposed across the C ABI boundary, so the waker argument has type Ptr<u8> everywhere, including 7.8.8 (likewise for rok_io_register and rok_timer_register). The exact layout of the pointed-to object is determined by the coroutine implementation.

8.5.4. Scheduler area

The task runtime (spawn, join, cancel, executor) is rok code in rok::core::task (stdlib 2.18). The runtime provides only the executor slot, thread and parking symbols below.

rok_task_worker_* / rok_time_now_ns

Functions that store and retrieve the current thread's executor worker (stdlib 2.18) in the TLS slot rok_tls_task_worker (8.3.1), plus a clock. Thread-safety: caller-thread-only (rok_time_now_ns is thread-safe).

extern "rok" {
    fn rok_task_worker_present() -> i32;       // 1 if this thread is a worker
    fn rok_task_worker_get() -> usize;         // worker object address, 0 if none
    fn rok_task_worker_set(w: usize);          // a live GC object or 0. Pinned while installed
    fn rok_task_worker_clear();                // = rok_task_worker_set(0)
    fn rok_time_now_ns() -> i64;               // monotonic clock (ns)
}

rok_thread_spawn

Creates a 1:1 OS thread. Thread-safety: thread-safe.

extern "rok" {
    fn rok_thread_spawn(f: RokFn, stack_size: usize, id: u64) -> usize;
    fn rok_thread_new_id() -> u64;
    fn rok_thread_id() -> u64;                        // worker id of the current thread
    fn rok_thread_sleep_ns(ns: i64);
    fn rok_thread_yield_now();
}

rok_thread_spawn runs the rok closure f (a RokFn, passed by value) on an OS thread (stack_size 0 means the default). id is a value obtained from rok_thread_new_id and becomes the new thread's rok_tls_worker_id. The return value is a join handle, or 0 if creation fails. env is a GC root until the thread finishes.

rok_task_hooks_install / rok_task_key

The executor of rok::core::task installs hooks into the runtime. The runtime's I/O driver, timer, and rok_coro_park / rok_coro_unpark use these hooks to wake and suspend tasks. Thread-safety: thread-safe.

extern "rok" {
    fn rok_task_hooks_install(
        io: extern "C" fn(fd: i32, interest: u32, waker: Ptr<u8>),
        timer: extern "C" fn(deadline_ns: u64, waker: Ptr<u8>),
        park: extern "C" fn(key: Ptr<u8>, waker: Ptr<u8>),
        unpark: extern "C" fn(key: Ptr<u8>),
        restart: extern "C" fn(),               // restart executor work lost with a failed REPL cell
    );                                          // the first installation wins
    unsafe fn rok_task_key(obj: Ptr<u8>) -> usize;   // GC object address as a park key
}

rok_task_key uses the address as a key, so it sets NOMOVE (8.1.3) on the object. The object is never moved afterwards. Identity comparisons used only within a call use buf_same (stdlib 2.9), which pins nothing.

rok_sync_wait / rok_sync_wake

Waiting and waking for synchronization primitives (Mutex, Condvar, Once). Thread-safety: thread-safe.

extern "rok" {
    fn rok_sync_wait(buf: Ptr<u8>, index: usize, expected: i32, timeout_ns: i64);   // wait while the GcBuf<i32> element equals expected. timeout_ns < 0 means forever
    fn rok_sync_wake(buf: Ptr<u8>, index: usize);                                    // wake all threads waiting on that element
}

rok_sync_wait may return without a reason to wake, so the caller re-checks its condition. While waiting, the thread is considered stopped with respect to the GC.

rok_thread_join

Thread-safety: thread-safe.

extern "rok" {
    fn rok_thread_join(handle: usize) -> i32;          // 0 on success
}

rok_thread_detach / rok_cpu_count

Thread-safety: thread-safe.

extern "rok" {
    fn rok_thread_detach(handle: usize) -> i32;   // OS resources are released when it ends; can no longer be joined. 0 on success
    fn rok_cpu_count() -> usize;                  // number of online CPUs (at least 1) - size of the par pool
}

rok_static_init_begin / rok_static_init_end

Back the lazy initialization of statics whose initializer is not const (5.3). Thread-safety: thread-safe.

extern "rok" {
    fn rok_static_init_begin(state: Ptr<u8>, at: ConstPtr<u8>, at_len: usize) -> i32;   // 1: caller runs the initializer, 0: already ready
    fn rok_static_init_end(state: Ptr<u8>);            // publish initialization completion
}

state is the state byte of that static (0 new, 1 in progress, 2 ready). If another thread is initializing, rok_static_init_begin waits until it finishes, during which the thread is considered stopped with respect to the GC. at / at_len is the path:line:col of the code reading the static; an initializer that reads its own static panics there (4.14.13).

rok_gc_add_root

extern "rok" {
    fn rok_gc_add_root(word: ConstPtr<usize>);   // register a static's reference word as a GC root until process exit
}

Startup code generated by the compiler calls this for every static that holds a GC reference.

rok_os_* - OS helpers (internal, subject to change)

Internal symbols backing the standard library rok::std (fs, io, env, process). They are not covered by ABI stability and may change, so user code does not call them directly.

rok_os_strerror  rok_os_open      rok_os_close     rok_os_read      rok_os_read_all
rok_os_write     rok_os_write_all rok_os_fsync     rok_os_ftruncate rok_os_lseek
rok_os_dup       rok_os_fchmod    rok_os_flock     rok_os_copy_fd   rok_os_stat
rok_os_mkdir     rok_os_unlink    rok_os_rename    rok_os_link      rok_os_opendir
rok_os_readdir   rok_os_closedir  rok_os_getcwd    rok_os_chdir     rok_os_realpath
rok_os_stdin_line rok_os_stdin_read rok_os_flush_stdout
rok_os_argc      rok_os_arg       rok_os_env_at    rok_os_getenv    rok_os_setenv
rok_os_spawn     rok_os_waitpid   rok_os_kill      rok_os_getpid    rok_os_getppid
rok_os_abort

rok_deque_* - work-stealing deque

The lock-free Chase-Lev deque of the task scheduler (7.6.2). The deque and its ring are GC objects traced by the GC, so elements that exist only inside a deque are kept alive. Thread-safety: push / pop only from the owning thread; steal and len from any thread.

extern "rok" {
    fn rok_deque_new() -> GcBuf<u8>;                                 // empty deque (GC object)
    unsafe fn rok_deque_push(d: Ptr<u8>, item: usize);               // owner: push at the end (GC reference)
    unsafe fn rok_deque_pop(d: Ptr<u8>, out: Ptr<u8>, index: usize) -> i32;    // owner: newest element into out[index], 1 if any
    unsafe fn rok_deque_steal(d: Ptr<u8>, out: Ptr<u8>, index: usize) -> i32;  // oldest element into out[index], 1 if any
    unsafe fn rok_deque_len(d: Ptr<u8>) -> usize;                    // element count (approximate under contention)
}

out is a GcBuf holding GC references; the result is written at index. These functions contain no safepoints.

8.5.5. I/O / Timer area

rok_io_register / rok_timer_register

Let C or low-level code register a task to be woken when an fd is ready or at a point in time. Internally they use the reactor (7.7) and the executor's timer. Calling them outside a task executor aborts.

Thread-safety: thread-safe.

extern "rok" {
    fn rok_io_register(fd: i32, interest: u32, waker: Ptr<u8>);
    fn rok_timer_register(deadline: u64, waker: Ptr<u8>);
}
  • fd: the file descriptor.
  • interest: 1 = read, 2 = write (the two may be ORed).
  • waker: a single-element GcBuf<Waker>. Registration is edge-triggered and wakes only once (spurious wakeups are possible). If the fd is already ready or cannot be watched, it wakes immediately.
  • deadline: an absolute time (ns) on the rok_time_now_ns clock. When that time is reached, the waker is woken once.

rok_io_* - reactor (internal, subject to change)

Internal symbols making up the reactor of the I/O driver. They are not covered by ABI stability and may change. Thread-safety: thread-safe.

extern "rok" {
    fn rok_io_poller() -> i32;                    // the process's kqueue / epoll fd (created on first call)
    fn rok_io_watch(fd: i32) -> i32;              // register fd for read / write edge-triggered (re-registering is harmless), 0 or -errno
    fn rok_io_poll(out: Ptr<u8>, max: usize) -> i32;   // blocks until something changes (parked for the GC), event count or -errno
}

rok_io_poll writes up to max events into out, a GcBuf<i64>, as fd * 2 + direction (read 0, write 1).

rok_net_* - socket helpers (internal, subject to change)

Internal symbols backing the standard library rok::std::net (stdlib 3.2). They are not covered by ABI stability and may change, so user code does not call them directly. Sockets are always created non-blocking, and failures are returned as -errno.

rok_net_socket     rok_net_bind       rok_net_listen     rok_net_connect
rok_net_sock_error rok_net_accept     rok_net_addr       rok_net_send
rok_net_send_to    rok_net_recv_from  rok_net_shutdown   rok_net_setopt
rok_net_multicast_v4 rok_net_wait     rok_net_resolve    rok_net_gai_strerror

rok_net_wait is a poll(2) with which a blocking call waits for readiness (a native section for the GC); rok_net_resolve is getaddrinfo.

8.5.6. Error propagation (no symbols)

throw / try / catch from Error control flow have no runtime symbols. A throwing function returns the error as part of its return value (8.6.5), and the call site checks that value and branches (8.9.6). There is therefore no runtime call to throw an error or fetch the current error, and there are no TLS slots or jmp_buf for error handling.

8.5.7. Diagnostic area

Exit codes below (101 etc.) reach the host as plain failure on wasm32-wasi (4.16.4).

rok_panic

Thread-safety: thread-safe (callable from any thread; the process exits immediately).

extern "rok" {
    fn rok_panic(msg: ConstPtr<u8>, msg_len: usize);   // does not return
}

An unrecoverable panic; fatal. This function does not return: it prints the message to stderr and terminates the process with exit code 101. catch and finally are not run.

rok_panic_str

Thread-safety: thread-safe.

extern "rok" {
    fn rok_panic_str(msg: str) -> !;          // msg is (ptr, len)
}

Same as rok_panic but takes the message as a str. It prints no location and is used only for panics internal to the runtime.

rok_panic_at

Thread-safety: thread-safe.

extern "rok" {
    #[track_caller]
    fn rok_panic_at(msg: str) -> !;
}

The declaration is #[track_caller] (6.2.12), so the symbol takes the caller's location as a hidden trailing str argument ("path:line:col"): rok_panic_at(msg, at).

Prints in the format of 4.14.13 and exits with code 101. Core's panic and compiler-generated panics (bounds checks, overflow, division by zero, etc.) call this symbol.

rok_uncaught_error / rok_uncaught_error_at

extern "rok" {
    fn rok_uncaught_error(what: str) -> !;              // uncaught exception: print what to stderr, exit code 1
    fn rok_uncaught_error_at(what: str, at: str) -> !;  // followed by a "  --> <at>" line
}

Handle errors propagated out of the entry point (4.16.4). The declarations live in core.

rok_oom

Thread-safety: thread-safe.

extern "rok" {
    fn rok_oom() -> !;
}

Prints fatal error: out of memory to stderr and exits with code 101. Allocation failures inside the runtime and core's oom() both go through this symbol. The runtime cannot call rok functions by name, so on allocation failure it uses this symbol directly instead of calling the function in the oom_fn slot.

Stack overflow

When a thread's stack overflows, the runtime flushes stdout (if possible), prints the following to stderr and aborts (SIGABRT). Unlike a panic, there is no unwinding and no finally execution.

thread '<name>' has overflowed its stack

<name> is the thread name given with thread::Builder::name (also set as the OS thread name, 7. Runtime). This covers the main thread (rok_runtime_init), rok_thread_spawn threads (including executor workers, and deep await chains), and C threads that called rok_thread_init. The runtime records the guard range below each thread's stack in the TLS slot rok_tls_stack_guard (8.3.1) and installs an alternate signal stack (sigaltstack). Only SIGSEGV / SIGBUS within that range is reported as a stack overflow; other faults follow the default behavior. On wasm a module cannot catch the trap; the host reports it (the shipped JS host prints the message above).

8.5.8. Symbol naming conventions

The area of each symbol prefix is summarized below.

PrefixArea
rok_gc_*GC
rok_runtime_*Runtime in general
rok_thread_*OS threads
rok_coro_*Coroutines
rok_task_*Tasks
rok_io_*I/O
rok_timer_*Timers
rok_panicDiagnostics

This prefix convention avoids symbol collisions with external libraries and user code. The tables of 8.5.1 - 8.5.7 list the normative runtime symbols; the runtime may export further implementation-defined rok_* symbols (e.g. rok_fmt_*, rok_os_*, rok_stdio_*, rok_static_init_*).

8.5.9. Compatibility of function signatures

Each function signature is part of the ABI. The change policy is:

  • minor: only compatible changes are allowed; adding arguments is handled through function wrappers.
  • major: breaking changes are possible, and the ABI version hash changes.

See the stability policy in the overview for details.

8.5.10. Calling convention

All functions above use the target platform's C ABI calling convention, e.g. System V x86_64, AAPCS aarch64, MS x64 Windows.

8.5.11. Example: slow call from the TLAB fast path

; LLVM IR (when the fast path fails)
%t = call ptr @llvm.threadlocal.address(ptr @rok_tls)
%cursor_p = getelementptr i8, ptr %t, i64 8          ; tlab_cursor at p
%cursor = load i8*, i8** %cursor_p
%new_cursor = getelementptr i8, i8* %cursor, i64 24
%limit_p = getelementptr i8, ptr %t, i64 16          ; tlab_limit at 2p
%limit = load i8*, i8** %limit_p
%check = icmp ugt i8* %new_cursor, %limit
br i1 %check, label %slow, label %fast

slow:
  %obj = call i8* @rok_gc_alloc_slow(i64 24, i64 8, i32 12345)
  br label %cont

fast:
  store i8* %new_cursor, i8** %cursor_p
  br label %cont

cont:
  %obj_phi = phi i8* [%obj_slow, %slow], [%cursor, %fast]
  ; ... continue

The example shows the LLVM IR structure combining the fast path and the slow path.

8.5.12. Auxiliary symbols (appendix, non-normative)

Important

This section is non-normative. The symbols below are auxiliary symbols exported by the reference runtime (rokr). They are not part of the main ABI tables (8.5.1 ~ 8.5.11) and carry no stability promise; the runtime may export further implementation-defined rok_* symbols (8.5.8).

Signatures use the same notation as the main text (Ptr<T> = mutable raw pointer, ConstPtr<T> = read-only raw pointer). String arguments are NUL-terminated C strings.

extern "rok" {
    // memory
    fn rok_free(ptr: Ptr<u8>);                             // null ignored
    fn rok_load_i32(ptr: ConstPtr<i32>) -> i32;
    fn rok_load_i64(ptr: ConstPtr<i64>) -> i64;
    fn rok_load_f64(ptr: ConstPtr<f64>) -> f64;
    fn rok_store_i32(ptr: Ptr<i32>, val: i32);
    fn rok_store_i64(ptr: Ptr<i64>, val: i64);
    fn rok_store_f64(ptr: Ptr<f64>, val: f64);

    // C strings
    fn rok_str_len(s: ConstPtr<u8>) -> usize;              // null → 0

    // GC
    fn rok_gc_collect_young();                             // force a minor collection
    fn rok_gc_weak_new(target: Ptr<u8>) -> Ptr<u8>;       // a weak cell (type id 3) to target
    fn rok_gc_weak_get(cell: Ptr<u8>) -> Ptr<u8>;         // the target, or null once freed

    // process exit
    fn rok_set_exit_hook(hook: extern "C" fn(i32) | null);
        // called with the exit status, on the ending thread, before a panic,
        // uncaught error, exit or (Windows) stack overflow ends the process;
        // the process ends when it returns

    // REPL host (session save / restore)
    fn rok_session_hold(timeout_ns: i64) -> i32;          // stop the world at a quiet moment: no poll / blocking job
                                                           //   running, no thread stopped inside a lock; 0: not within
                                                           //   timeout_ns (still requested)
    fn rok_session_unhold();                               // withdraw a pending or granted hold
    fn rok_session_resume();                               // resume the world after a hold
    fn rok_session_enter();                                // executor threads' gates around a poll / blocking job
    fn rok_session_leave();
    fn rok_session_pass();
    fn rok_lock_notes() -> i32;                            // 1 in a session host
    fn rok_lock_note(word: GcBuf<i32>, kind: i32) -> i32;  // kind: 0 Mutex, 1 read, 2 write, 3 running Once; 1 = noted
    fn rok_lock_drop(word: GcBuf<i32>);
    fn rok_world_threads(out: Ptr<u64>, cap: usize) -> usize;   // world stopped: OS ids of the other mutators (Windows)
    fn rok_session_restored(lost: ConstPtr<u64>, n: usize);     // session put back; n == SIZE_MAX: every other thread is gone
    fn rok_thread_lost(worker_id: u64) -> i32;             // 1 if that thread was found gone after a failed cell
    fn rok_io_quiesce();                                   // Windows: cancel in-flight I/O before memory is put back
    fn rok_watch_writes();                                 // Windows: write-watch the heap; call before runtime init
    static rok_page_epoch: u64;                            // Windows: bumped when watched pages change without a write
                                                           //   or a watched block is released
}

The following rules apply.

  • User code and the standard library must not depend on these symbols. Their signatures may change or be removed without notice.
  • They share the rok_ prefix but are not included in the area table of 8.5.8. To avoid name collisions, normative symbols of the specification do not reuse these names.
  • rok_panic does not belong to this appendix. The signature rok_panic(msg: ConstPtr<u8>, msg_len: usize) in 8.5.7 is authoritative, and an implementation exporting a different signature is non-conformant.

Edit this page on GitHub