From 8dfad2b6def77406edfd9ab4ef99a1e12602b1e5 Mon Sep 17 00:00:00 2001 From: Tim Whiting Date: Sun, 5 May 2024 00:43:07 -0600 Subject: [PATCH] update to c api --- lib/std/core/cextern.kk | 208 +++++++++++++++++++++++++++++----- lib/std/core/inline/cextern.h | 10 ++ 2 files changed, 188 insertions(+), 30 deletions(-) diff --git a/lib/std/core/cextern.kk b/lib/std/core/cextern.kk index 701b11055..5f7646caf 100644 --- a/lib/std/core/cextern.kk +++ b/lib/std/core/cextern.kk @@ -2,59 +2,207 @@ module std/core/cextern import std/num/int32 import std/core/types +import std/core/int extern import - c file "inline/cextern.h" + c header-file "inline/cextern.h" +// A managed koka pointer to C memory +// Owned values can be freely passed around, captured in lambdas, escape their scope, etc. pub alias owned-c = extern-owned + +// A borrowed koka pointer to C memory during a scope s +// Borrowed values are only guaranteed valid during their scope, should not escape the scope +// For example an `owned-c>` should only allow borrowed references to the middle of an array during a scope where the owned pointer will not be dropped pub alias borrowed-c = extern-borrowed -pub type c-array -pub alias c-null = intptr_t -pub inline extern cnull(): c-null - c inline "(kk_addr_t)NULL" +// A raw pointer to C memory +pub value struct c-pointer + ptr: intptr_t -pub fun array/malloc(n: int32, ?size-of: (c-null) -> int32): owned-c> - int/malloc(n, size-of(cnull())).c-own - -pub fun single/malloc(?size-of: (c-null) -> int32): owned-c - int/malloc(1.int32, size-of(cnull())).c-own +// An opaque type to designate c-array types in Koka +pub type c-array -pub fun array/malloc-c(n: int32, ?size-of: (c-null) -> int32): owned-c> - int/malloc-c(n, size-of(cnull())).c-own-free-calloc +// A type alias for a null pointer of type `t` +pub alias c-null = c-pointer -pub fun single/malloc-c(?size-of: (c-null) -> int32): owned-c - int/malloc-c(1.int32, size-of(cnull())).c-own-free-calloc +// The null pointer in C +pub inline fun cnull(): c-null + C-pointer(0.intptr_t) +// Allocate `n*size-of` bytes of memory using kk_malloc and return a pointer to the allocated memory extern int/malloc(n: int32, size-of: int32): intptr_t c inline "(kk_addr_t)kk_malloc(#1*#2, kk_context())" +// Allocate `n*size-of` bytes of memory using C's malloc and return a pointer to the allocated memory extern int/malloc-c(n: int32, size-of: int32): intptr_t c inline "(kk_addr_t)malloc(#1*#2)" -// Transform a c ptr into a managed koka value, which will be freed when koka's reference count reaches 0 -pub extern c-own(c: intptr_t): owned-c +// Allocate a single element of type `t` using `kk_malloc` and return a managed pointer +// Type `t` should: +// - Be an opaque type in Koka corresponding to a C type (e.g. `pub type cstruct` with no members) +// - Have a `size-of` function that returns the size of the structure in bytes +pub fun single/malloc(?size-of: (c-null) -> int32): owned-c + int/malloc(1.int32, size-of(cnull())).c-own-extern + +// Allocate `n` elements of type `t` using `kk_malloc` and return a managed pointer to the array +// Type `t` should: +// - Be an opaque type in Koka corresponding to a C type (e.g. `pub type cstruct` with no members) +// - Have a `size-of` function that returns the size of the structure in bytes +pub fun array/malloc(n: int32, ?size-of: (c-null) -> int32): owned-c> + int/malloc(n, size-of(cnull())).c-own-extern + +// Allocate a single element of type `t` using C's `malloc` and return a managed pointer +// Type `t` should: +// - Be an opaque type in Koka corresponding to a C type (e.g. `pub type cstruct` with no members) +// - Have a `size-of` function that returns the size of the structure in bytes +pub fun single/malloc-c(?size-of: (c-null) -> int32): owned-c + int/malloc-c(1.int32, size-of(cnull())).c-own-free-calloc-extern + +// Allocate `n` elements of type `t` using C's `malloc` and return a managed pointer to the array +// Type `t` should: +// - Be an opaque type in Koka corresponding to a C type (e.g. `pub type cstruct` with no members) +// - Have a `size-of` function that returns the size of the structure in bytes +pub fun array/malloc-c(n: int32, ?size-of: (c-null) -> int32): owned-c> + int/malloc-c(n, size-of(cnull())).c-own-free-calloc-extern + +// !!!WARNING!!! UNSAFE API +// Allocate `n` elements of type `t` using `kk_malloc` and return a managed pointer to the array +// Type `t` should: +// - Be an opaque type in Koka corresponding to a C type (e.g. `pub type cstruct` with no members) +// - Have a `size-of` function that returns the size of the structure in bytes +// +// NOTES: +// Prefer using `array/malloc` or `single/malloc` instead of this function which return a managed pointer. +// Raw `c-pointer` should be used in low-level generated koka ffi functions since the pointer is unknown to be managed or not. +// Conversion routines for `owned-c` and `borrowed-c` then should be used to get the raw pointers to be used in the ffi functions +// Higher level apis to c libraries should then provide an interface using `owned-c` and `borrowed-c` instead of `c-pointer` +pub fun ptr/unsafe-malloc(n: int32, ?size-of: (c-null) -> int32): c-pointer + C-pointer(int/malloc(n, size-of(cnull()))) + +// !!!WARNING!!! UNSAFE API +// Allocate `n` elements of type `t` using C's `malloc` and return a managed pointer to the array +// Type `t` should: +// - Be an opaque type in Koka corresponding to a C type (e.g. `pub type cstruct` with no members) +// - Have a `size-of` function that returns the size of the structure in bytes +// +// NOTES: +// Prefer using `array/malloc-c` or `single/malloc-c` instead of this function which return a managed pointer. +// Raw `c-pointer` should be used in low-level generated koka ffi functions since the pointer is unknown to be managed or not. +// Conversion routines for `owned-c` and `borrowed-c` then should be used to get the raw pointers to be used in the ffi functions +// Higher level apis to c libraries should then provide an interface using `owned-c` and `borrowed-c` instead of `c-pointer` +pub fun ptr/unsafe-malloc-c(n: int32, ?size-of: (c-null) -> int32): c-pointer + C-pointer(int/malloc-c(n, size-of(cnull()))) + +// Transform a C ptr into a managed koka value, which will be freed by `kk_free` when koka's reference count reaches 0 +extern c-own-extern(c: intptr_t): a c inline "kk_cptr_raw_box(&kk_free_fun, (void *)#1, kk_context())" -// Transform a c ptr into a managed koka value, which will be freed when koka's reference count reaches 0 -pub extern c-own-free-calloc(c: intptr_t): owned-c +// Transform a C ptr into a managed koka value, which will be freed by C's `free` when koka's reference count reaches 0 +extern c-own-free-calloc-extern(c: intptr_t): a c inline "kk_cptr_raw_box(&kk_free_calloc, (void *)#1, kk_context())" -// Transform a c ptr into a koka value that holds the c reference without freeing it -// The pointer should be valid -pub extern c-borrow(c: intptr_t, f: forall borrowed-c -> e a): e a +// Transform a C ptr `c` into a koka value that holds the c reference without freeing it +// The pointer should be valid for the duration of the callback `f`. +extern c-borrow-extern(c: intptr_t, f: b -> e a): e a c "kk_borrow_ptr" -// Transform a koka owned c value into a c ptr (keeping the koka reference alive during the scope of the function) -pub extern owned/with-ptr(^t: owned-c, f: intptr_t -> e a): e a +// !!!WARNING!!!: Extremely unsafe API (needed for `c-borrow`), get approval to use anywhere else. +extern unsafe-cast(b: b): a + c inline "#1" + +// Transform an unmanaged C ptr into a managed koka reference to C memory +// Ensure the pointer is not going to be freed by C code, otherwise use `c-borrow` instead +// Also ensure the memory was allocated using `kk_malloc` +pub fun c-own(t: c-pointer): owned-c + t.ptr.c-own-extern + +// Transform an unmanaged C ptr into a managed koka reference to C memory +// Ensure the pointer is not going to be freed by C code, otherwise use `c-borrow` instead +// Also ensure the memory was allocated using C's `malloc` +pub fun c-own-free-calloc(t: c-pointer): owned-c + t.ptr.c-own-free-calloc-extern + +// Transform an unmanaged C ptr into a borrowed koka reference to C memory +// The pointer must be guaranteed to be valid for the duration of the callback `f` +pub fun c-borrow(c: c-pointer, f: forall borrowed-c -> e a): e a + c-borrow-extern(c.ptr, fn(p) f(p.unsafe-cast())) + +// Transform a koka `owned-c` managed pointer into a C ptr +// Keeps the koka reference alive during the scope of the callback `f` +extern owned/with-ptr-extern(^t: b, f: intptr_t -> e a): e a c "kk_owned_with_ptr" -// Transform a koka owned value into a c ptr (keeping the koka reference alive during the scope of the function) -pub extern borrowed/with-ptr(^t: borrowed-c, f: intptr_t -> e a): e a - c "kk_borrowed_with_ptr" +// Transform a koka `owned-c` managed pointer into a C ptr +// Keeps the koka reference alive during the scope of the callback `f` +pub fun owned/with-ptr(t: owned-c, f: c-pointer -> e a): e a + owned/with-ptr-extern(t, fn(p) f(C-pointer(p))) -pub fun c-array/with-ptr(^t: owned-c>, idx: ssize_t, f: forall borrowed-c -> e a, ?size-of: (c-null) -> int32): e a - offset/with-ptr(t, idx, fn(p) c-borrow(p, f), size-of(cnull())) +// Transform a koka `borrowed-c` managed pointer into a C ptr +// Keeps the koka reference alive during the scope of the callback `f` +extern borrowed/with-ptr-extern(^t: b, f: intptr_t -> e a): e a + c "kk_borrowed_with_ptr" -extern offset/with-ptr(^t: owned-c>, idx: ssize_t, f: intptr_t -> e a, size-of: int32): e a - c "kk_owned_with_ptr_idx" \ No newline at end of file +// Transform a koka `borrowed-c` managed pointer into a C ptr +// Keeps the koka reference alive during the scope of the callback `f` +pub fun borrowed/with-ptr(t: borrowed-c, f: c-pointer -> e a): e a + borrowed/with-ptr-extern(t, fn(i) f(C-pointer(i))) + +// !!!WARNING!!! Extremely UNSAFE API +// Get the raw C pointer from a `borrowed-c` managed pointer to use immediately in an ffi function +// This doesn't return a typed pointer, and accepts any boxed type as input, so it is very dangerous +// Use `borrowed/with-ptr` most of the time and +// `borrow/use-ffi-ptr` if directly passing to an safe ffi call +pub extern unsafe-borrowed-ffi-ptr-extern(c: b): intptr_t + c inline "(kk_addr_t)kk_cptr_unbox_borrowed(#1, kk_context())" + +// !!!WARNING!!! UNSAFE API +// Get the raw C pointer from an `borrowed-c` managed pointer to use immediately in an ffi function +// Not safe to pass around Koka code +// However, since an immediate use is still within the scope of the `borrowed-c` it is safe +// This is due borrowed pointers being guaranteed to be valid during their whole scope (the lambda enclosing the call to this method) +// A similar api for `owned-c` is not possible since converting an owned pointer to a raw pointer could allow the owned pointer to be freed if this was its last use +// For owned pointers use `owned/with-ptr` instead +pub fun borrow/use-ffi-ptr(c: borrowed-c): c-pointer + C-pointer(c.unsafe-borrowed-ffi-ptr-extern) + +// Transform a koka `owned-c` managed pointer to an array into a C ptr pointing to the element at index `idx` of type `t` and size `size-of(cnull())` +// Keeps the koka reference alive during the scope of the callback `f` +// This is guaranteed due to be this being an external function (`f` is not inlineable), and `t` being borrowed +extern offset/with-ptr(^t: b, idx: ssize_t, f: intptr_t -> e a, size-of: int32): e a + c "kk_owned_with_ptr_idx" + +// Transform a koka `owned-c` managed pointer to an array into a C ptr pointing to the element at index `idx` of type `t` and size `size-of(cnull())` +// Keeps the koka reference alive during the scope of the callback `f` +pub fun c-array/with-ptr(t: owned-c>, idx: ssize_t, f: forall borrowed-c -> e a, ?size-of: (c-null) -> int32): e a + offset/with-ptr(t, idx, fn(p) c-borrow(C-pointer(p), f), size-of(cnull())) + +// Transform an assumed pointer to a C string into a Koka string +// Copies the memory +extern ptr/to-string(ptr: intptr_t): string + c inline "kk_string_alloc_raw((const char *)#1, false, kk_context())" + +// Transform an unmanaged `c-pointer` into a Koka string +// Copies the memory +pub fun cptr/to-string(c: c-pointer): string + ptr/to-string(c.ptr) + +// Transform an assumed pointer to a C string of length len into a Koka string +// Copies the memory +// Assume the array is non-null terminated and adds the terminating character +extern strlen-ptr/to-string(ptr: intptr_t, len: int64): string + c inline "kk_string_alloc_raw_buff(#2, (const char *)#1, false, kk_context())" + +// Transform an unmanaged `c-pointer` into a Koka string of length len +// Copies the memory +// Assume the array is non-null terminated and adds the terminating character +pub fun cptr-len/to-string(c: c-pointer, len: int64): string + strlen-ptr/to-string(c.ptr, len) + +// Borrows the c pointer to a koka managed string for the duration of the callback `f` +extern ptr/with-c-string(^s: string, f: intptr_t -> e a): e a + c "kk_with_c_string" + +// Borrows the c pointer to a koka managed string for the duration of the callback `f` +pub fun cptr/with-c-string(^s: string, f: forall borrowed-c -> e a): e a + with-c-string(s, fn(p) c-borrow(C-pointer(p), f)) \ No newline at end of file diff --git a/lib/std/core/inline/cextern.h b/lib/std/core/inline/cextern.h index d0e5fb0c1..7262b6d80 100644 --- a/lib/std/core/inline/cextern.h +++ b/lib/std/core/inline/cextern.h @@ -24,4 +24,14 @@ kk_box_t kk_owned_with_ptr_idx(kk_box_t owned, kk_ssize_t idx, kk_function_t f, uint8_t* cptr = (uint8_t*)kk_cptr_raw_unbox_borrowed(owned, kk_context()); kk_addr_t cptr_idx = (kk_addr_t)(cptr + (idx*size)); return kk_function_call(kk_box_t,(kk_function_t,kk_addr_t,kk_context_t*), f, (f, cptr_idx, kk_context()), kk_context()); +} + +kk_string_t kk_string_alloc_raw_buff(kk_ssize_t len, char* s, bool free, kk_context_t* ctx){ + s[len] = 0; + return kk_string_alloc_raw_len(len, s, free, ctx); +} + +kk_box_t kk_with_c_string(kk_string_t s, kk_function_t f, kk_context_t* _ctx){ + kk_addr_t cptr = (kk_addr_t)kk_string_cbuf_borrow(s, NULL, kk_context()); + return kk_function_call(kk_box_t,(kk_function_t,kk_addr_t,kk_context_t*), f, (f, cptr, kk_context()), kk_context()); } \ No newline at end of file