rokdocs
v0.1

8.5. Extern "C" 심볼

librokr.a 가 노출하는 심볼 의 시그니처를 정의합니다. 컴파일러가 생성한 코드와 표준 라이브러리가 이 심볼을 부릅니다.

rok 쪽 선언은 런타임 ABI extern "rok" (5.9.2) 이며, toolchain 이 rok::core::rt 에 pub(pkg) 로 둡니다. 아래 블록의 fn 값은 RokFn (8.5.0a), 그 밖의 값은 C ABI 와 같습니다. str 은 struct { const uint8_t *ptr; size_t len; } 이며, str 을 받거나 돌려주는 런타임 함수는 RokFn 처럼 16 바이트 구조체에 대한 플랫폼 C ABI 를 따릅니다 (8.6.3): ABI 가 허용하면 레지스터로 값 전달하고, 레지스터와 스택에 나뉘지 않으며, Windows x64 에서는 참조로 넘깁니다. 프로그램은 런타임 심볼을 직접 선언할 수 있습니다 (extern "rok" / extern "C"). 그 선언은 여기서 정한 시그니처를 가져야 하며, 다르면 에러입니다.

중요

모든 심볼은 rok_ 접두사로 시작합니다. 이는 외부 라이브러리나 사용자 코드와의 심볼 충돌을 회피하기 위함입니다.

8.5.0. Thread-safety 분류

각 심볼의 정의 옆에는 Thread-safety 표기가 따라붙습니다. 이는 컴파일러가 emit 한 코드와 런타임 사이의 contract 의 일부로, 어떤 호출 컨텍스트에서 안전하게 호출할 수 있는지를 명시합니다. 사용 가능한 분류는 다음 6 가지입니다.

분류의미
thread-safe다중 thread 가 동시에 호출해도 안전 (런타임이 내부 동기화)
caller-thread-only호출자 thread 의 컨텍스트에 묶임 - 다른 thread 가 그 호출자를 대신할 수 없음
process-init프로세스 시작 시점, mt 진입 전 단 한 번
process-shutdown프로세스 종료 시점, mt 종료 후 단 한 번
per-thread-init각 worker thread 의 시작 시점에 그 thread 가 자기 자신에 대해 한 번
per-thread-shutdown각 worker thread 의 종료 시점에 그 thread 가 자기 자신에 대해 한 번

caller-thread-only 는 함수의 효과가 호출자의 TLS 슬롯이나 stack frame 에 결합되어 있어 다른 thread 에서 그 호출자를 대신할 수 없다는 뜻입니다. 함수 자체가 reentrant 하지 않다는 의미는 아니며, 다중 thread 가 각자 자기 호출자에 대해 동시에 호출하는 것은 허용됩니다.

8.5.0a. RokFn - 함수 값의 C ABI 형태

rok 함수 값 {code, env} (4.4) 를 런타임 심볼에 넘길 때의 C 표현입니다.

typedef struct {
    void *code;   // env 를 첫 인자로 받는 코드 포인터
    void *env;    // 캡처 환경 (이름 있는 함수는 NULL)
} RokFn;

RokFn 은 C 구조체로 값 전달 됩니다. 실제 레지스터 배치는 타깃 C ABI 의 16 바이트 구조체 규칙을 따릅니다.

8.5.1. GC 영역

rok_gc_alloc_slow

할당 의 TLAB slow path 에 해당합니다. Thread-safety: thread-safe.

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

각 인자와 반환값의 의미는 다음과 같습니다.

  • size: 객체의 총 크기로, 16 바이트의 헤더에 페이로드를 더한 뒤 정렬한 값입니다.
  • align: 객체의 정렬 요구로, 보통 8 이며 #[repr(align(N))] 가 지정되면 더 커집니다.
  • type_id: 객체의 타입 ID 입니다.
  • 반환: 헤더 자리부터 시작하는 객체의 GC 참조 (GcBuf<u8>, 런타임 ABI 의 GC 포인터) 를 돌려주며, type_id 와 gc_flags 는 함수가 채워둡니다.

null 을 반환하지 않습니다. 메모리 부족은 fatal (rok_oom) 이며, isize::MAX 를 넘는 요청은 fatal 에러 "allocation too large" (종료 코드 101) 입니다.

rok_alloc_raw / rok_realloc_raw

GC 밖의 raw 메모리를 할당합니다 (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;
}

zeroed 가 0 이 아니면 할당한 메모리를 0 으로 채웁니다. rok_alloc_raw 는 size 가 0 이거나 align 이 2 의 거듭제곱이 아니거나 할당에 실패하면 null (0) 을 반환하며, abort 하지 않습니다. rok_realloc_raw 도 실패 시 null 을 반환합니다. 실패 처리는 호출자 (rok::bare::alloc) 가 합니다.

rok_gc_wb_slow

배리어와 루트 추적 의 write barrier slow path 에 해당합니다. Thread-safety: caller-thread-only (TLS 의 wb log buffer 에 기록).

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

인자 obj 는 변경된 슬롯이 속한 객체의 포인터이며, 별도의 반환값은 없습니다. 효과로는 remembered set 에 obj 를 추가하고, log 가 차면 flush 합니다.

rok_gc_register_type

ScanTable 의 타입을 등록합니다. Thread-safety: process-init.

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

호출 시점은 프로세스 시작 시점이며, mt 진입 전이므로 동기화 불필요.

rok_gc_pin / rok_gc_unpin

FFI 객체의 pinning 을 담당합니다 (객체 레이아웃과 메모리 공간, extern 블록 (FFI 선언) 참조). Thread-safety: thread-safe.

extern "rok" {
    fn rok_gc_pin(obj: Ptr<u8>) -> Ptr<u8>;       // 고정된 객체 (같은 주소) 반환
    fn rok_gc_unpin(obj: Ptr<u8>);
}

extern "C" 함수나 blocking 런타임 호출에 넘긴 GC 포인터는 호출이 끝날 때까지 인자가 루트에 있으므로 따로 pin 하지 않아도 옮겨지지 않습니다 (7.4.1). C 코드가 호출 뒤에도 포인터를 보관할 때만 pin 이 필요합니다. pin 은 객체에 PINNED 를 켜서 evacuation 대상에서 빼고 GC 가 살려 두게 하며, unpin 은 그것을 풉니다.

rok_gc_collect

명시적인 GC 트리거이며, 드물게 사용됩니다. Thread-safety: thread-safe.

extern "rok" {
    fn rok_gc_collect();
}

full 수집을 하고, 수집이 끝난 뒤 돌아옵니다.

rok_gc_secret_new / rok_gc_secret_wipe

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 는 WIPE (8.1.3) 를 켠 type id 1 (참조 없음) 의 LOS 객체를 할당합니다. 0 으로 채운 payload 는 len 을 8 의 배수로 올린 바이트를 담습니다. 먼저 수집할 수 있으며, isize::MAX 를 넘는 요청은 fatal error "allocation too large" 입니다. rok_gc_secret_wipe 는 payload 전체를 volatile store 로 덮어쓰고 compiler fence 를 둡니다. 할당하지도 block 하지도 않으므로 safepoint 가 아닙니다 (8.4). 수집기는 객체를 해제하기 전에 같은 방식으로 payload 를 덮어씁니다.

8.5.2. Runtime 영역

rok_runtime_init

런타임을 초기화합니다. Thread-safety: process-init.

extern "rok" {
    fn rok_runtime_init();
}

호출 시점은 프로세스 시작 직후이자 main() 이전이며, 효과로는 worker 풀을 생성하고 GC 공간을 mmap 하며 I/O driver 를 초기화합니다. 컴파일러가 생성한 C-ABI main 은 이 호출 직후에 rok.register_types 를 호출합니다 (8.2.13).

런타임은 이 밖에 thread-local 블록 rok_tls 를 export 합니다 (8.3.8).

rok_runtime_shutdown

런타임을 종료합니다. Thread-safety: process-shutdown.

extern "rok" {
    fn rok_runtime_shutdown();
}

main() 이 끝난 직후에 부릅니다. 다른 모든 rok 스레드는 지금 실행 중인 런타임 / C 코드를 마친 뒤 다음 safepoint 에서 영영 멈춥니다. 그 뒤 worker 풀을 끝내고 남은 자원을 정리합니다.

rok_exit

프로세스를 즉시 종료합니다. Thread-safety: thread-safe.

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

rok::std::env::exit 가 이 심볼을 부릅니다. finally 와 Drop 은 실행하지 않습니다 (4.16.4).

문자열 helper

FromStr 과 str::bytes 를 뒷받침하는 내부 helper 입니다 (stdlib 2.15). Thread-safety: thread-safe. str 인자는 머리말의 구조체 형태입니다.

extern "rok" {
    fn rok_str_eq(a: str, b: str) -> i32;                            // 같으면 1, 아니면 0
    fn rok_str_byte(s: str, i: usize) -> u8;                         // i 번째 바이트 (경계 검사는 호출자)
    fn rok_str_slice(s: str, start: usize, end: usize, at: str) -> str;  // s[start:end]. 범위 / UTF-8 경계가 틀리면 at 위치로 panic
    fn rok_str_parse_int(s: str, radix: u32, signed: i32, bits: u32, out: GcBuf<i64>) -> i32;
                                        // 0 ok, 1 빈 문자열, 2 잘못된 자릿수, 3 범위 밖; 값의 비트는 out[0]
    fn rok_str_parse_float(s: str, out: GcBuf<f64>) -> i32;           // 0 ok, 2 잘못됨, 3 범위 초과 (유한한 숫자가 타입 범위를 넘음); 값은 out[0]
}

rok_runtime_yield

Safepoint 프로토콜 의 yield 함수, 즉 safepoint poll 의 slow path 입니다. Thread-safety: caller-thread-only (호출자 mutator 가 자기 yield_flag 를 polling 한 결과로 호출).

extern "rok" {
    fn rok_runtime_yield();
}

rok_enter_native / rok_leave_native

native bracket 의 slow path 입니다 (8.4.10). Thread-safety: caller-thread-only.

extern "rok" {
    fn rok_enter_native();                // C 로 들어감: 이 스레드는 PARKED
    fn rok_leave_native();                // C 에서 돌아옴: 진행 중인 수집을 기다림
}

컴파일러는 모든 extern "C" 호출 앞뒤에 fast path 를 inline 으로 냅니다. mutator 상태에 PARKED / RUNNING 을 seq_cst 로 쓰고 yield flag 를 봅니다. flag 가 켜져 있을 때만 이 함수를 부릅니다.

rok_native_reenter / rok_native_resume

모든 extern "C" fn 정의의 입구와 출구이며, C callback 이 mutator 로 돌게 합니다. Thread-safety: caller-thread-only.

extern "rok" {
    fn rok_native_reenter() -> u32;       // 스레드가 PARKED (C 안) 였으면 native 를 떠남; 그랬는지 반환
    fn rok_native_resume(parked: u32);    // reenter 가 떠났으면 다시 native 로
}

rok_thread_init / rok_thread_shutdown

worker thread 의 lifecycle 을 담당합니다. Thread-safety: rok_thread_init 은 per-thread-init, rok_thread_shutdown 은 per-thread-shutdown.

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

각 worker thread 의 시작 및 종료 시점에 호출되어, TLS 슬롯 의 TLS 를 초기화하거나 정리합니다.

내부 심볼 무리

다음 무리는 참조 런타임의 내부 helper 이며 ABI 계약에 속하지 않습니다: rok_fmt_*, rok_str_* helper, rok_char_*, rok_nat_*, rok_stdio_*, rok_time_*, rok_gc_stats_*, rok_buf_*, rok_cstr_len.

safepoint 가 아닌 함수

rok 쪽에서는 #[no_safepoint] 가 붙은 extern "rok" 블록에 선언합니다 (5.9.2).

다음 런타임 함수는 할당도 block 도 하지 않음이 보장되므로, 그 호출은 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, rok_char_* 속성 / 대소문자 함수, rok_fmt_len, rok_f64_*, rok_f32_*.

8.5.3. Coroutine 영역

rok_coro_frame_alloc

코루틴 의 frame 을 할당합니다. Thread-safety: thread-safe.

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

rok_gc_alloc_slow 와 거의 동일하지만, frame 전용으로 별도 처리가 가능합니다.

rok_coro_park / rok_coro_unpark

컴파일러는 이 두 심볼을 emit 하지 않습니다 (await 는 rok::core::task 의 parking registry 를 씁니다). 런타임은 C 나 저수준 코드를 위해 제공합니다. frame (아무 GC 객체) 의 주소가 parking key 이며, rok_coro_unpark(frame) 은 그 key 에 park 한 waker 를 모두 깨웁니다. task executor 밖에서 부르면 abort 합니다.

Thread-safety: rok_coro_park 는 caller-thread-only (호출자 코루틴 자신을 park), rok_coro_unpark 는 thread-safe (다른 thread 에서 호출 가능).

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

rok_coro_park 는 코루틴을 일시 중단시키고 waker 를 등록합니다. rok_coro_unpark 는 코루틴을 재개시키며 task 큐에 push 합니다.

여기서 waker 는 깨우는 함수와 컨텍스트의 묶음, 곧 런타임의 Waker 객체 (2.20.2) 를 가리키는 불투명 포인터 (Ptr<u8>) 입니다. C ABI 경계에서는 Waker 의 내부 표현을 노출하지 않으므로, 7.8.8 을 비롯한 모든 자리에서 waker 인자의 타입은 Ptr<u8> 로 통일됩니다 (rok_io_register, rok_timer_register 도 같음). 포인터가 가리키는 객체의 정확한 레이아웃은 코루틴 구현이 결정합니다.

8.5.4. Scheduler 영역

task 런타임 (spawn, join, cancel, executor) 은 rok::core::task 의 rok 코드입니다 (stdlib 2.18). 런타임은 아래의 executor 슬롯, 스레드, parking 심볼만 제공합니다.

rok_task_worker_* / rok_time_now_ns

현재 스레드의 executor worker (stdlib 2.18) 를 TLS 슬롯 rok_tls_task_worker (8.3.1) 에 두고 꺼내는 함수와 시계입니다. Thread-safety: caller-thread-only (rok_time_now_ns 는 thread-safe).

extern "rok" {
    fn rok_task_worker_present() -> i32;       // 이 스레드가 worker 이면 1
    fn rok_task_worker_get() -> usize;         // worker 객체 주소, 없으면 0
    fn rok_task_worker_set(w: usize);          // 살아 있는 GC 객체 또는 0. 설치하는 동안 pin
    fn rok_task_worker_clear();                // = rok_task_worker_set(0)
    fn rok_time_now_ns() -> i64;               // monotonic clock (ns)
}

rok_thread_spawn

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
    fn rok_thread_sleep_ns(ns: i64);
    fn rok_thread_yield_now();
}

rok_thread_spawn 은 rok 클로저 f (아래 RokFn, 값 전달) 를 OS 스레드에서 실행합니다 (stack_size 0 은 기본값). id 는 rok_thread_new_id 로 받은 값이며 새 스레드의 rok_tls_worker_id 가 됩니다. 반환값은 join handle 이고, 생성에 실패하면 0 입니다. 스레드가 끝날 때까지 env 는 GC root 입니다.

rok_task_hooks_install / rok_task_key

rok::core::task 의 executor 가 런타임에 hook 을 설치합니다. 런타임의 I/O driver, timer, rok_coro_park / rok_coro_unpark 는 이 hook 으로 task 를 깨우고 멈춥니다. 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(),               // 실패한 REPL 셀과 함께 사라진 executor 작업을 재시작
    );                                          // 처음 설치한 것이 이김
    unsafe fn rok_task_key(obj: Ptr<u8>) -> usize;   // GC 객체 주소를 park 키로
}

rok_task_key 는 주소를 키로 쓰므로 그 객체에 NOMOVE (8.1.3) 를 켭니다. 그 객체는 이후 옮겨지지 않습니다. 호출 안에서만 쓰는 동일성 비교는 buf_same (stdlib 2.9) 을 쓰며 아무것도 고정하지 않습니다.

rok_sync_wait / rok_sync_wake

동기화 primitive (Mutex, Condvar, Once) 의 대기와 깨우기입니다. Thread-safety: thread-safe.

extern "rok" {
    fn rok_sync_wait(buf: Ptr<u8>, index: usize, expected: i32, timeout_ns: i64);   // GcBuf<i32> 원소가 expected 인 동안 대기. timeout_ns < 0 이면 무한
    fn rok_sync_wake(buf: Ptr<u8>, index: usize);                                    // 그 원소에서 대기 중인 스레드를 모두 깨움
}

rok_sync_wait 는 깨운 이유 없이 돌아올 수 있으므로 호출자가 조건을 다시 확인합니다. 대기하는 동안 그 스레드는 GC 에 대해 정지한 것으로 봅니다.

rok_thread_join

Thread-safety: thread-safe.

extern "rok" {
    fn rok_thread_join(handle: usize) -> i32;          // 0 이면 성공
}

rok_thread_detach / rok_cpu_count

Thread-safety: thread-safe.

extern "rok" {
    fn rok_thread_detach(handle: usize) -> i32;   // 끝나면 OS 자원 반환, 더 이상 join 불가. 0 이면 성공
    fn rok_cpu_count() -> usize;                  // 온라인 CPU 수 (최소 1) - par 풀의 크기
}

rok_static_init_begin / rok_static_init_end

const 가 아닌 초기값을 가진 static 의 지연 초기화 (5.3) 를 뒷받침합니다. Thread-safety: thread-safe.

extern "rok" {
    fn rok_static_init_begin(state: Ptr<u8>, at: ConstPtr<u8>, at_len: usize) -> i32;   // 1: 호출자가 초기화식 실행, 0: 이미 준비됨
    fn rok_static_init_end(state: Ptr<u8>);            // 초기화 완료 공개
}

state 는 그 static 의 상태 바이트 (0 새로, 1 진행 중, 2 준비됨) 입니다. 다른 스레드가 초기화 중이면 rok_static_init_begin 은 끝날 때까지 기다리며, 그동안 GC 에 대해 정지한 것으로 봅니다. at / at_len 은 그 static 을 읽는 코드의 path:line:col 이며, 자기 static 을 읽는 초기화식은 그 자리에서 panic 합니다 (4.14.13).

rok_gc_add_root

extern "rok" {
    fn rok_gc_add_root(word: ConstPtr<usize>);   // static 의 참조 word 를 프로세스 끝까지 GC root 로 등록
}

컴파일러가 만든 시작 코드가 GC 참조를 담는 모든 static 에 대해 호출합니다.

rok_os_* - OS helper (내부, 변경 가능)

표준 라이브러리 rok::std (fs, io, env, process) 를 뒷받침하는 내부 심볼입니다. ABI 안정 대상이 아니며 바뀔 수 있으므로 사용자 코드는 직접 부르지 않습니다.

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

task 스케줄러 (7.6.2) 의 lock-free Chase-Lev deque 입니다. deque 와 그 ring 은 GC 객체이며 GC 가 추적합니다. 그래서 deque 안에만 있는 원소도 살아 있습니다. Thread-safety: push / pop 은 소유 thread 만, steal 과 len 은 어느 thread 든.

extern "rok" {
    fn rok_deque_new() -> GcBuf<u8>;                                 // 빈 deque (GC 객체)
    unsafe fn rok_deque_push(d: Ptr<u8>, item: usize);               // 소유자: 끝에 넣음 (GC 참조)
    unsafe fn rok_deque_pop(d: Ptr<u8>, out: Ptr<u8>, index: usize) -> i32;    // 소유자: 가장 새 원소를 out[index] 에, 있으면 1
    unsafe fn rok_deque_steal(d: Ptr<u8>, out: Ptr<u8>, index: usize) -> i32;  // 가장 오래된 원소를 out[index] 에, 있으면 1
    unsafe fn rok_deque_len(d: Ptr<u8>) -> usize;                    // 원소 수 (경합 중의 근사값)
}

out 은 GC 참조를 담는 GcBuf 이며 index 자리에 결과를 씁니다. 이 함수들 안에는 safepoint 가 없습니다.

8.5.5. I/O / Timer 영역

rok_io_register / rok_timer_register

C 나 저수준 코드가 task 를 fd 준비나 시각에 깨우도록 등록합니다. 내부적으로는 reactor (7.7) 와 executor 의 timer 를 씁니다. task executor 밖에서 부르면 abort 합니다.

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: file descriptor 입니다.
  • interest: 1 = 읽기, 2 = 쓰기 (둘을 OR 할 수 있음).
  • waker: 원소 하나인 GcBuf<Waker> 입니다. 등록은 edge-triggered 이고 한 번만 깨웁니다 (가짜 깨움 가능). 이미 준비되어 있거나 감시할 수 없는 fd 면 곧바로 깨웁니다.
  • deadline: rok_time_now_ns 시계 기준의 절대 시각 (ns) 입니다. 그 시각에 닿으면 waker 를 한 번 깨웁니다.

rok_io_* - reactor (내부, 변경 가능)

I/O 드라이버 의 reactor 를 이루는 내부 심볼입니다. ABI 안정 대상이 아니며 바뀔 수 있습니다. Thread-safety: thread-safe.

extern "rok" {
    fn rok_io_poller() -> i32;                    // 프로세스의 kqueue / epoll fd (처음 부를 때 생성)
    fn rok_io_watch(fd: i32) -> i32;              // fd 를 읽기 / 쓰기 edge-triggered 로 등록 (다시 걸어도 무해), 0 또는 -errno
    fn rok_io_poll(out: Ptr<u8>, max: usize) -> i32;   // 변화가 올 때까지 막힘 (GC 에는 park), 사건 수 또는 -errno
}

rok_io_poll 은 사건을 GcBuf<i64> 인 out 에 fd * 2 + 방향 (읽기 0, 쓰기 1) 으로 최대 max 개 씁니다.

rok_net_* - 소켓 helper (내부, 변경 가능)

표준 라이브러리 rok::std::net (stdlib 3.2) 을 뒷받침하는 내부 심볼입니다. ABI 안정 대상이 아니며 바뀔 수 있으므로 사용자 코드는 직접 부르지 않습니다. 소켓은 늘 non-blocking 으로 만들어지며, 실패는 -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 는 blocking 호출이 준비를 기다리는 poll(2) 이며 (GC 에는 native 구간), rok_net_resolve 는 getaddrinfo 입니다.

8.5.6. 에러 전달 (심볼 없음)

에러 제어 흐름 의 throw / try / catch 에는 런타임 심볼이 없습니다. throwing 함수는 에러를 반환값의 일부로 돌려주고 (8.6.5), 호출 지점이 그 값을 검사해 분기합니다 (8.9.6). 따라서 에러를 던지거나 현재 에러를 꺼내기 위해 런타임을 호출하는 일은 없으며, 에러 처리를 위한 TLS 슬롯이나 jmp_buf 도 두지 않습니다.

8.5.7. Diagnostic 영역

아래 종료 코드 (101 등) 는 wasm32-wasi 에서 host 에 단순 실패로 전해집니다 (4.16.4).

rok_panic

Thread-safety: thread-safe (어떤 thread 에서든 호출 가능, 호출 즉시 프로세스 종료).

extern "rok" {
    fn rok_panic(msg: ConstPtr<u8>, msg_len: usize);   // 반환 안 함
}

회복 불가능한 panic 으로, fatal 입니다. 이 함수는 반환하지 않으며, 메시지를 stderr 에 출력한 뒤 프로세스를 종료 코드 101 로 종료시킵니다. catch 나 finally 는 실행되지 않습니다.

rok_panic_str

Thread-safety: thread-safe.

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

rok_panic 과 같으며 메시지를 str 로 받습니다. 위치를 출력하지 않으며, 런타임 내부의 panic 만 씁니다.

rok_panic_at

Thread-safety: thread-safe.

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

선언이 #[track_caller] (6.2.12) 이므로, 심볼은 호출자의 위치를 숨은 마지막 str 인자 ("path:line:col") 로 받습니다: rok_panic_at(msg, at).

4.14.13 의 형식으로 출력하고 종료 코드 101 로 끝냅니다. core 의 panic 과 컴파일러가 생성한 panic (경계 검사, overflow, 0 으로 나누기 등) 이 이 심볼을 부릅니다.

rok_uncaught_error / rok_uncaught_error_at

extern "rok" {
    fn rok_uncaught_error(what: str) -> !;              // 미처리 예외: what 을 stderr 에 출력하고 종료 코드 1
    fn rok_uncaught_error_at(what: str, at: str) -> !;  // 그 뒤에 "  --> <at>" 줄
}

진입점 밖으로 전파된 에러를 처리합니다 (4.16.4). 선언은 core 에 있습니다.

rok_oom

Thread-safety: thread-safe.

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

fatal error: out of memory 를 stderr 에 출력하고 종료 코드 101 로 종료합니다. 런타임 안의 할당 실패와 core 의 oom() 이 모두 이 심볼을 거칩니다. 런타임은 rok 함수를 이름으로 부를 수 없으므로, 할당 실패 시 oom_fn 슬롯의 함수를 부르지 않고 이 심볼을 직접 씁니다.

스택 오버플로

스레드의 스택이 넘치면 런타임은 stdout 을 (가능하면) flush 하고 stderr 에 다음을 출력한 뒤 abort (SIGABRT) 합니다. panic 과 달리 unwind 나 finally 실행은 없습니다.

thread '<name>' has overflowed its stack

<name> 은 thread::Builder::name 으로 준 스레드 이름입니다 (OS 스레드 이름으로도 설정됨, 7. 런타임). main 스레드 (rok_runtime_init), rok_thread_spawn 스레드 (executor worker 포함, 깊은 await 사슬도), rok_thread_init 을 부른 C 스레드가 모두 대상입니다. 런타임은 각 스레드의 스택 아래 guard 범위를 TLS 슬롯 rok_tls_stack_guard (8.3.1) 에 기록하고 대체 신호 스택 (sigaltstack) 을 둡니다. 그 범위 안의 SIGSEGV / SIGBUS 만 스택 오버플로로 보고, 그 밖의 fault 는 기본 동작을 따릅니다. wasm 에서는 모듈이 trap 을 잡을 수 없으며 host 가 보고합니다 (함께 제공되는 JS host 는 위 메시지를 출력).

8.5.8. 심볼 명명 규약

심볼의 접두사별 영역은 다음 표와 같이 정리됩니다.

Prefix영역
rok_gc_*GC 관련
rok_runtime_*런타임 일반
rok_thread_*OS thread 관련
rok_coro_*코루틴 관련
rok_task_*task 관련
rok_io_*I/O
rok_timer_*타이머
rok_panic진단

이러한 접두사 규약을 통해 외부 라이브러리나 사용자 코드와의 심볼 충돌을 피합니다. 8.5.1 - 8.5.7 의 표가 규범적인 런타임 심볼이며, 런타임은 구현이 정하는 rok_* 심볼 (예: rok_fmt_*, rok_os_*, rok_stdio_*, rok_static_init_*) 을 더 내보낼 수 있습니다.

8.5.9. 함수 시그니처의 호환성

각 함수의 시그니처는 ABI 의 일부를 이룹니다. 변경 시의 정책은 다음과 같습니다.

  • minor: 호환 가능한 변경만 허용되며, 인자 추가는 함수 wrapper 를 통해 처리합니다.
  • major: 호환을 깨는 변경이 가능하며, 이때 ABI 버전 해시가 변환됩니다.

자세한 사항은 개요 의 안정성 정책을 참조합니다.

8.5.10. Calling convention

위 모든 함수의 calling convention 은 타깃 플랫폼의 C ABI 를 따릅니다. 예를 들어 System V x86_64, AAPCS aarch64, MS x64 Windows 등이 해당합니다.

8.5.11. 사용 예: TLAB fast path 의 slow 호출

; LLVM IR (fast path 실패 시)
%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]
  ; ... 계속

위 예시는 fast path 와 slow path 가 결합된 LLVM IR 구조를 보여줍니다.

8.5.12. 보조 심볼 (부록, 비규범)

중요

이 절은 비규범 (non-normative) 입니다. 아래 심볼은 참조 런타임 (rokr) 이 내보내는 보조 심볼입니다. ABI 본 표 (8.5.1 ~ 8.5.11) 의 일부가 아니며 안정성 약속이 없습니다. 런타임은 구현이 정하는 rok_* 심볼을 더 내보낼 수 있습니다 (8.5.8).

시그니처는 본문과 같은 표기 (Ptr<T> = 가변 raw 포인터, ConstPtr<T> = 읽기 전용 raw 포인터) 를 따릅니다. 문자열 인자는 NUL 종료 C 문자열입니다.

extern "rok" {
    // 메모리
    fn rok_free(ptr: Ptr<u8>);                             // null 무시
    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 문자열
    fn rok_str_len(s: ConstPtr<u8>) -> usize;              // null → 0

    // GC
    fn rok_gc_collect_young();                             // minor 수집을 강제
    fn rok_gc_weak_new(target: Ptr<u8>) -> Ptr<u8>;       // target 을 가리키는 weak cell (type id 3)
    fn rok_gc_weak_get(cell: Ptr<u8>) -> Ptr<u8>;         // 대상, 해제되었으면 null

    // 프로세스 종료
    fn rok_set_exit_hook(hook: extern "C" fn(i32) | null);
        // panic, 처리되지 않은 에러, exit, (Windows) stack overflow 가 프로세스를
        // 끝내기 전에, 끝나는 스레드에서 종료 상태와 함께 호출; hook 이 반환하면
        // 프로세스가 끝남

    // REPL host (세션 저장 / 복원)
    fn rok_session_hold(timeout_ns: i64) -> i32;          // 조용한 순간에 world 를 멈춤: 실행 중인 poll / blocking job 이
                                                           //   없고 lock 안에서 멈춘 스레드가 없을 때. 0: timeout_ns 안에
                                                           //   못 함 (요청은 남음)
    fn rok_session_unhold();                               // 대기 중이거나 얻은 hold 를 거둠
    fn rok_session_resume();                               // hold 뒤에 world 를 재개
    fn rok_session_enter();                                // poll / blocking job 둘레의 executor 스레드 gate
    fn rok_session_leave();
    fn rok_session_pass();
    fn rok_lock_notes() -> i32;                            // 세션 host 에서 1
    fn rok_lock_note(word: GcBuf<i32>, kind: i32) -> i32;  // kind: 0 Mutex, 1 read, 2 write, 3 실행 중인 Once; 1 = 기록함
    fn rok_lock_drop(word: GcBuf<i32>);
    fn rok_world_threads(out: Ptr<u64>, cap: usize) -> usize;   // world 정지 중: 다른 mutator 의 OS id (Windows)
    fn rok_session_restored(lost: ConstPtr<u64>, n: usize);     // 세션을 되돌림. n == SIZE_MAX: 다른 스레드는 모두 사라짐
    fn rok_thread_lost(worker_id: u64) -> i32;             // 실패한 셀 뒤에 그 스레드가 사라졌으면 1
    fn rok_io_quiesce();                                   // Windows: 메모리를 되돌리기 전에 진행 중인 I/O 취소
    fn rok_watch_writes();                                 // Windows: heap 을 write-watch. 런타임 초기화 전에 호출
    static rok_page_epoch: u64;                            // Windows: watch 하는 page 가 쓰기 없이 바뀌거나
                                                           //   watch 하는 block 이 해제되면 증가
}

다음 규칙이 적용됩니다.

  • 사용자 코드와 표준 라이브러리는 이 심볼에 의존해서는 안 됩니다. 시그니처는 예고 없이 바뀌거나 제거될 수 있습니다.
  • rok_ prefix 를 공유하지만 8.5.8 의 영역 표에는 포함되지 않습니다. 이름 충돌을 피하기 위해 사양의 규범 심볼은 위 이름을 재사용하지 않습니다.
  • rok_panic 은 이 부록에 속하지 않습니다. 시그니처는 8.5.7 의 rok_panic(msg: ConstPtr<u8>, msg_len: usize) 가 권위이며, 구현이 이와 다른 시그니처를 내보내는 것은 부적합 (non-conformance) 입니다.

GitHub에서 이 페이지 고치기