diff --git a/ffitest/test.janet b/ffitest/test.janet index 0eb4a199..e3d99d87 100644 --- a/ffitest/test.janet +++ b/ffitest/test.janet @@ -68,4 +68,40 @@ (assert (= 19 (double-lots 1 2 3 4 5 6 7 8 9 10))) (assert (= 204 (float-fn 8 4 17))) +# +# Struct reading and writing +# + +(defn check-round-trip + [t value] + (def buf (native-write t value)) + (def same-value (native-read t buf)) + (assert (deep= value same-value) + (string/format "round trip %j (got %j)" value same-value))) + +(check-round-trip :bool true) +(check-round-trip :bool false) +(check-round-trip :void nil) +(check-round-trip :void nil) +(check-round-trip :s8 10) +(check-round-trip :s8 0) +(check-round-trip :s8 -10) +(check-round-trip :u8 10) +(check-round-trip :u8 0) +(check-round-trip :s16 10) +(check-round-trip :s16 0) +(check-round-trip :s16 -12312) +(check-round-trip :u16 10) +(check-round-trip :u16 0) +(check-round-trip :u32 0) +(check-round-trip :u32 10) +(check-round-trip :u32 0xFFFF7777) +(check-round-trip :s32 0x7FFF7777) +(check-round-trip :s32 0) +(check-round-trip :s32 -1234567) + +(def s (native-struct :s8 :s8 :s8 :float)) +(check-round-trip s [1 3 5 123.5]) +(check-round-trip s [-1 -3 -5 -123.5]) + (print "Done.") diff --git a/src/core/ffi.c b/src/core/ffi.c index 10c152bc..05aee7ec 100644 --- a/src/core/ffi.c +++ b/src/core/ffi.c @@ -219,6 +219,14 @@ static JanetFFIPrimType decode_ffi_prim(const uint8_t *name) { if (!janet_cstrcmp(name, "ssize")) return JANET_FFI_TYPE_INT32; #endif /* aliases */ + if (!janet_cstrcmp(name, "s8")) return JANET_FFI_TYPE_INT8; + if (!janet_cstrcmp(name, "u8")) return JANET_FFI_TYPE_UINT8; + if (!janet_cstrcmp(name, "s16")) return JANET_FFI_TYPE_INT16; + if (!janet_cstrcmp(name, "u16")) return JANET_FFI_TYPE_UINT16; + if (!janet_cstrcmp(name, "s32")) return JANET_FFI_TYPE_INT32; + if (!janet_cstrcmp(name, "u32")) return JANET_FFI_TYPE_UINT32; + if (!janet_cstrcmp(name, "s64")) return JANET_FFI_TYPE_INT64; + if (!janet_cstrcmp(name, "u64")) return JANET_FFI_TYPE_UINT64; if (!janet_cstrcmp(name, "char")) return JANET_FFI_TYPE_INT8; if (!janet_cstrcmp(name, "short")) return JANET_FFI_TYPE_INT16; if (!janet_cstrcmp(name, "int")) return JANET_FFI_TYPE_INT32; @@ -291,11 +299,13 @@ static void *janet_ffi_getpointer(const Janet *argv, int32_t n) { /* Write a value given by some Janet values and an FFI type as it would appear in memory. * The alignment and space available is assumed to already be sufficient */ -static void janet_ffi_write_one(void *to, const Janet *argv, int32_t n, JanetFFIType type) { +static void janet_ffi_write_one(void *to, const Janet *argv, int32_t n, JanetFFIType type, int recur) { + if (recur == 0) janet_panic("recursion too deep"); switch (type.prim) { case JANET_FFI_TYPE_VOID: - default: - janet_panic("nyi"); + if (!janet_checktype(argv[n], JANET_NIL)) { + janet_panicf("expected nil, got %v", argv[n]); + } break; case JANET_FFI_TYPE_STRUCT: { JanetView els = janet_getindexed(argv, n); @@ -310,7 +320,7 @@ static void janet_ffi_write_one(void *to, const Janet *argv, int32_t n, JanetFFI size_t align = type_align(tp); size_t size = type_size(tp); cursor = ((cursor + align - 1) / align) * align; - janet_ffi_write_one(to + cursor, els.items, i, tp); + janet_ffi_write_one(to + cursor, els.items, i, tp, recur - 1); cursor += size; } } @@ -354,6 +364,64 @@ static void janet_ffi_write_one(void *to, const Janet *argv, int32_t n, JanetFFI } } +/* Read a value from memory and construct a Janet data structure that can be passed back into + * the interpreter. This should be the inverse to janet_ffi_write_one. It is assumed that the + * size of the data is correct. */ +static Janet janet_ffi_read_one(const uint8_t *from, JanetFFIType type, int recur) { + if (recur == 0) janet_panic("recursion too deep"); + switch (type.prim) { + default: + case JANET_FFI_TYPE_VOID: + return janet_wrap_nil(); + case JANET_FFI_TYPE_STRUCT: + { + JanetFFIStruct *st = type.st; + Janet *tup = janet_tuple_begin(st->field_count); + size_t cursor = 0; + for (uint32_t i = 0; i < st->field_count; i++) { + JanetFFIType tp = st->fields[i]; + size_t align = type_align(tp); + size_t size = type_size(tp); + cursor = ((cursor + align - 1) / align) * align; + tup[i] = janet_ffi_read_one(from + cursor, tp, recur - 1); + cursor += size; + } + return janet_wrap_tuple(janet_tuple_end(tup)); + } + case JANET_FFI_TYPE_DOUBLE: + return janet_wrap_number(((double *)(from))[0]); + case JANET_FFI_TYPE_FLOAT: + return janet_wrap_number(((float *)(from))[0]); + case JANET_FFI_TYPE_PTR: + return janet_wrap_pointer(((void **)(from))[0]); + case JANET_FFI_TYPE_BOOL: + return janet_wrap_boolean(((bool *)(from))[0]); + case JANET_FFI_TYPE_INT8: + return janet_wrap_number(((int8_t *)(from))[0]); + case JANET_FFI_TYPE_INT16: + return janet_wrap_number(((int16_t *)(from))[0]); + case JANET_FFI_TYPE_INT32: + return janet_wrap_number(((int32_t *)(from))[0]); + case JANET_FFI_TYPE_UINT8: + return janet_wrap_number(((uint8_t *)(from))[0]); + case JANET_FFI_TYPE_UINT16: + return janet_wrap_number(((uint16_t *)(from))[0]); + case JANET_FFI_TYPE_UINT32: + return janet_wrap_number(((uint32_t *)(from))[0]); +#ifdef JANET_INT_TYPES + case JANET_FFI_TYPE_INT64: + return janet_wrap_s64(((int64_t *)(from))[0]); + case JANET_FFI_TYPE_UINT64: + return janet_wrap_u64(((uint64_t *)(from))[0]); +#else + case JANET_FFI_TYPE_INT64: + return janet_wrap_number(((int64_t *)(from))[0]); + case JANET_FFI_TYPE_UINT64: + return janet_wrap_number(((uint64_t *)(from))[0]); +#endif + } +} + static int is_fp_type(JanetFFIType type) { return type.prim == JANET_FFI_TYPE_DOUBLE || type.prim == JANET_FFI_TYPE_FLOAT; } @@ -528,7 +596,7 @@ static Janet janet_ffi_sysv64(JanetFFISignature *signature, void *function_point to = stack + arg.offset; break; } - janet_ffi_write_one(to, argv, n, arg.type); + janet_ffi_write_one(to, argv, n, arg.type, 64); } /* !!ACHTUNG!! */ @@ -652,17 +720,32 @@ JANET_CORE_FN(cfun_ffi_buffer_write, JanetBuffer *buffer = janet_optbuffer(argv, argc, 2, el_size); janet_buffer_extra(buffer, el_size); memset(buffer->data, 0, el_size); - janet_ffi_write_one(buffer->data, argv, 1, type); + janet_ffi_write_one(buffer->data, argv, 1, type, 64); buffer->count += el_size; return janet_wrap_buffer(buffer); } + +JANET_CORE_FN(cfun_ffi_buffer_read, + "(native-read ffi-type bytes &opt offset)", + "Parse a native struct out of a buffer and convert it to normal Janet data structures. " + "This function is the inverse of `native-write`.") { + janet_arity(argc, 2, 3); + JanetFFIType type = decode_ffi_type(argv[0]); + size_t el_size = type_size(type); + JanetByteView bytes = janet_getbytes(argv, 1); + size_t offset = (size_t) janet_optnat(argv, argc, 2, 0); + if ((size_t) bytes.len < offset + el_size) janet_panic("read out of range"); + return janet_ffi_read_one(bytes.bytes, type, 64); +} + void janet_lib_ffi(JanetTable *env) { JanetRegExt ffi_cfuns[] = { JANET_CORE_REG("native-signature", cfun_ffi_signature), JANET_CORE_REG("native-call", cfun_ffi_call), JANET_CORE_REG("native-struct", cfun_ffi_struct), JANET_CORE_REG("native-write", cfun_ffi_buffer_write), + JANET_CORE_REG("native-read", cfun_ffi_buffer_read), JANET_REG_END }; janet_core_cfuns_ext(env, NULL, ffi_cfuns);