From b0201131e2ebb75aec1d1dcd3dcd62f6d5743aa8 Mon Sep 17 00:00:00 2001 From: dzaima Date: Mon, 20 Mar 2023 16:33:22 +0200 Subject: [PATCH] =?UTF-8?q?array=20type=20support=20for=20=E2=80=A2FFI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/system.md | 1 + include/bqnffi.h | 2 +- src/README.md | 2 +- src/ffi.c | 128 +++++++++++++++++++++++++++-------------- test/ffi/ffiTest.c | 40 +++++++++++++ test/ffi/test.bqn | 27 +++++++-- test/ffi/test.expected | 7 +++ 7 files changed, 157 insertions(+), 50 deletions(-) diff --git a/docs/system.md b/docs/system.md index bba08bac..d6bdf6d3 100644 --- a/docs/system.md +++ b/docs/system.md @@ -124,6 +124,7 @@ That is, the supported types are: - scalars (e.g. `i8`, `u64`); - pointers to scalars (e.g. `*i8`, `&u64`); - conversions of either scalars, pointers to scalars, or opaque pointers (e.g. `u64:i32`, `*i64:i32`, `*:i8`, `**:c8`); +- arrays of scalars or structs (e.g. `[2]i32`, `[4]{i32,i32}`); - structs of any of the above (except `&`-pointers) or other structs (e.g. `{*i8,*{*u32:i8,u64:i32}}`), except structs that are within `&` themselves cannot contain any pointers other than converted opaque pointers (e.g. `*{*i32,u64}`, `&{*:i32,u64}`, and `&{i32,u64}` are fine, but `&{*i32,u64}` is not); - the `a` type, which maps to `BQNV` from [bqnffi.h](../include/bqnffi.h) (example usage in [FFI tests](../test/ffi/)). diff --git a/include/bqnffi.h b/include/bqnffi.h index fdcfd13b..ad3d02cb 100644 --- a/include/bqnffi.h +++ b/include/bqnffi.h @@ -30,7 +30,7 @@ BQNV bqn_call2(BQNV f, BQNV w, BQNV x); // Evaluate BQN code in a fresh environment BQNV bqn_eval(BQNV src); // src must be a character vector -BQNV bqn_evalCStr(const char* str); // evaluates the null-terminated UTF8-encoded str; equal to `BQNV s = bqn_makeUTF8Str(str, strlen(str)); result = bqn_eval(s); bqn_free(s);` +BQNV bqn_evalCStr(const char* str); // evaluates the null-terminated UTF8-encoded str; equal to `BQNV s = bqn_makeUTF8Str(strlen(str), str); result = bqn_eval(s); bqn_free(s);` // Read array data diff --git a/src/README.md b/src/README.md index b4bb3878..c5a3473a 100644 --- a/src/README.md +++ b/src/README.md @@ -73,7 +73,7 @@ src/ Type checks (all are safe to execute on any B object): test tag description heap-allocated isF64(x) F64_TAG a number no - isChr(x) C32_TAG a character no + isC32(x) C32_TAG a character no isAtm(x) [many] !isArr(x) depends isVal(x) [many] heap-allocated yes isFun(x) FUN_TAG a function yes diff --git a/src/ffi.c b/src/ffi.c index 066aa3ed..10b1e709 100644 --- a/src/ffi.c +++ b/src/ffi.c @@ -234,7 +234,7 @@ typedef struct BQNFFIEnt { } BQNFFIEnt; typedef struct BQNFFIType { struct Value; - union { u16 structSize; u16 staticAllocTotal; }; + union { u16 structSize; u16 arrCount; u16 staticAllocTotal; }; u8 ty; usz ia; BQNFFIEnt a[]; @@ -267,9 +267,11 @@ static const char* sty_names[] = { [sty_f32]="f32", [sty_f64]="f64" }; enum CompoundTy { - cty_ptr, - cty_repr, - cty_struct, + cty_ptr, // *... / &... + cty_repr, // something:type + cty_struct, // {...} + cty_starr, // struct-based array + cty_tlarr, // top-level array }; static B m_bqnFFIType(BQNFFIEnt** rp, u8 ty, usz ia) { @@ -292,7 +294,7 @@ static u32 readUInt(u32** p) { *p = c; return r; } -BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr) { // parse actual type +BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual type u32* c = *src; u32 c0 = *c++; ffi_type rt; @@ -300,7 +302,46 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr) { // parse actual type bool parseRepr=false, canRetype=false, mut=false; u32 myWidth = 0; // used if parseRepr switch (c0) { - default: thrM("FFI: Error parsing type"); + default: thrF("FFI: Error parsing type: Unexpected character '%c'", c0); + case '[': { + u32 n = readUInt(&c); + if (*c++!=']') thrM("FFI: Bad array type"); + if (n==0) thrM("FFI: 0-item arrays not supported"); + BQNFFIEnt e = ffi_parseTypeStr(&c, top, false); + + if (top) { // largely copy-pasted from `case '*': case '&':` + myWidth = sizeof(void*); + BQNFFIEnt* rp; ro = m_bqnFFIType(&rp, cty_tlarr, 1); + rp[0] = e; + if (n>U16_MAX) thrM("FFI: Top-level array too large; limit is 65535 elements"); + c(BQNFFIType, ro)->arrCount = n; + mut|= rp[0].mutates; + parseRepr = rp[0].canRetype; + rp[0].mutPtr = false; + rt = ffi_type_pointer; + } else { // largely copy-pasted from `case '{':` + BQNFFIEnt* rp; ro = m_bqnFFIType(&rp, cty_starr, n+1); + for (int i = 0; i < n; i++) rp[i] = e; + incBy(e.o, n-1); + rp[n].structData = NULL; + + TAlloc* ao = ARBOBJ(sizeof(ffi_type*) * (n+1)); + rp[n].structData = ao; + ffi_type** els = rt.elements = (ffi_type**) ao->data; + for (usz i = 0; i < n; i++) els[i] = &rp[i].t; + els[n] = NULL; + + rt.type = FFI_TYPE_STRUCT; + + TALLOC(size_t, offsets, n); + if (ffi_get_struct_offsets(FFI_DEFAULT_ABI, &rt, offsets) != FFI_OK) thrM("FFI: Failed getting array offsets"); + if (rt.size>=U16_MAX) thrM("FFI: Array too large; limit is 65534 bytes"); + for (usz i = 0; i < n; i++) rp[i].offset = offsets[i]; + c(BQNFFIType, ro)->structSize = rt.size; + TFREE(offsets); + } + break; + } case 'i': case 'u': case 'f': { u32 n = readUInt(&c); @@ -339,7 +380,7 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr) { // parse actual type } else { if (c0=='&') mut = true; BQNFFIEnt* rp; ro = m_bqnFFIType(&rp, cty_ptr, 1); - rp[0] = ffi_parseTypeStr(&c, true); + rp[0] = ffi_parseTypeStr(&c, true, false); mut|= rp[0].mutates; parseRepr = rp[0].canRetype; @@ -352,7 +393,7 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr) { // parse actual type case '{': { TSALLOC(BQNFFIEnt, es, 4); while (true) { - BQNFFIEnt e = TSADD(es, ffi_parseTypeStr(&c, false)); + BQNFFIEnt e = TSADD(es, ffi_parseTypeStr(&c, false, false)); if (e.mutates) thrM("FFI: Structs currently cannot contain mutable references"); u32 m = *c++; if (m=='}') break; @@ -431,7 +472,7 @@ BQNFFIEnt ffi_parseType(B arg, bool forRes) { // doesn't consume; parse argument if (side) side--; else side = 0; - t = ffi_parseTypeStr(&xp, false); + t = ffi_parseTypeStr(&xp, false, true); // printI(arg); printf(": "); printFFIType(stdout, t.o); printf("\n"); if (xp!=xpN) thrM("FFI: Bad type descriptor"); t.onW = side; @@ -496,14 +537,18 @@ void genObj(B o, B c, bool anyMut, void* ptr) { } } else { BQNFFIType* t = c(BQNFFIType, o); - if (t->ty==cty_ptr) { // *any / &any + if (t->ty==cty_ptr || t->ty==cty_tlarr) { // *any / &any B e = t->a[0].o; + if (!isArr(c)) { + if (isC32(e)) thrF("FFI: Expected array corresponding to \"*%S\"", sty_names[o2cG(e)]); + else thrM("FFI: Expected array corresponding to *{...}"); + } + usz ia = IA(c); + if (t->ty==cty_tlarr && t->arrCount!=ia) thrF("FFI: Incorrect item count of %s corresponding to \"[%s]...\"", ia, (usz)t->arrCount); if (isC32(e)) { // *num / &num inc(c); B cG; - if (!isArr(c)) thrF("FFI: Expected array corresponding to \"*%S\"", sty_names[o2cG(e)]); - usz ia = IA(c); bool mut = t->a[0].mutPtr; switch(o2cG(e)) { default: thrF("FFI: \"*%S\" argument type NYI", sty_names[o2cG(e)]); case sty_i8: cG = mut? taga(cpyI8Arr (c)) : toI8Any (c); break; @@ -518,13 +563,10 @@ void genObj(B o, B c, bool anyMut, void* ptr) { ffiObjs = vec_addN(ffiObjs, cG); *(void**)ptr = tyany_ptr(cG); - } else { + } else { // *{...} / &{...} / *[n]any BQNFFIType* t2 = c(BQNFFIType, e); - if (t2->ty != cty_struct) thrM("FFI: Unimplemented pointer element type"); + if (t2->ty!=cty_struct && t2->ty!=cty_starr) thrM("FFI: Unimplemented pointer element type"); - if (!isArr(c)) thrM("FFI: Expected array corresponding to *{...}"); - // *{...} / &{...} - usz ia = IA(c); usz elSz = t2->structSize; TALLOC(u8, dataAll, elSz*ia + sizeof(usz)); @@ -569,15 +611,15 @@ void genObj(B o, B c, bool anyMut, void* ptr) { *(void**)ptr = tyany_ptr(cG); ffiObjs = vec_addN(ffiObjs, cG); } - } else if (t->ty==cty_struct) { + } else if (t->ty==cty_struct || t->ty==cty_starr) { if (!isArr(c)) thrM("FFI: Expected array corresponding to a struct"); - if (IA(c)!=t->ia-1) thrM("FFI: Incorrect list length corresponding to a struct"); + if (IA(c)!=t->ia-1) thrF("FFI: Incorrect list length corresponding to %S: expected %s, got %s", t->ty==cty_struct? "a struct" : "an array", (usz)(t->ia-1), IA(c)); SGetU(c) for (usz i = 0; i < t->ia-1; i++) { BQNFFIEnt e = t->a[i]; genObj(e.o, GetU(c, i), anyMut, e.offset + (u8*)ptr); } - } else thrM("FFI: Unimplemented type"); + } else thrM("FFI: Unimplemented type (genObj)"); } } @@ -625,7 +667,7 @@ B readAny(BQNFFIEnt e, u8* ptr) { BQNFFIType* t = c(BQNFFIType, e.o); if (t->ty == cty_repr) { // cty_repr, scalar:x return readRe(t->a[0], ptr); - } else if (t->ty == cty_struct) { // {...} + } else if (t->ty==cty_struct || t->ty==cty_starr) { // {...} return readStruct(c(BQNFFIType, e.o), ptr); } } @@ -651,7 +693,7 @@ B buildObj(BQNFFIEnt ent, bool anyMut, B* objs, usz* objPos) { } } else { BQNFFIType* t2 = c(BQNFFIType, e); - assert(t2->ty == cty_struct); + assert(t2->ty==cty_struct || t2->ty==cty_starr); u8* dataAll = c(TAlloc,f)->data; void* dataStruct = dataAll+sizeof(usz); @@ -671,7 +713,7 @@ B buildObj(BQNFFIEnt ent, bool anyMut, B* objs, usz* objPos) { bool mut = t->a[0].mutPtr; if (!mut) return m_f64(0); return inc(f); - } else thrM("FFI: Unimplemented type"); + } else thrM("FFI: Unimplemented type (buildObj)"); } B libffiFn_c2(B t, B w, B x) { @@ -771,6 +813,21 @@ BQNFFIEnt ffi_parseType(B arg, bool forRes) { static u64 atomSize(B chr) { return o2cG(chr)==sty_a? sizeof(BQNV) : sizeof(ffi_arg)>8? sizeof(ffi_arg) : 8; } +static u64 calcStaticSize(BQNFFIEnt e) { + if (isC32(e.o)) { // scalar + return atomSize(e.o); + } else { + BQNFFIType* t = c(BQNFFIType, e.o); + if (t->ty==cty_ptr || t->ty==cty_tlarr) return sizeof(void*); // *any / &any / top-level [n]any + else if (t->ty==cty_struct || t->ty==cty_starr) return t->structSize; // {...} + else if (t->ty==cty_repr) { // any:any + B o2 = t->a[0].o; + if (isC32(o2)) return atomSize(o2); + if (c(BQNFFIType,o2)->ty != cty_ptr) thrM("FFI: Bad type with reinterpretation"); + return sizeof(void*); + } else thrM("FFI: Unimplemented type (size calculation)"); + } +} B ffiload_c2(B t, B w, B x) { usz xia = IA(x); @@ -796,7 +853,8 @@ B ffiload_c2(B t, B w, B x) { atomType = o2; goto calcRes; } - if (t->ty == cty_struct) { size = t->structSize; goto allocRes; } + if (t->ty==cty_struct) { size = t->structSize; goto allocRes; } + if (t->ty==cty_tlarr) thrM("FFI: Cannot return array"); thrM("FFI: Unimplemented result type"); calcRes:; size = atomSize(atomType); @@ -812,23 +870,7 @@ B ffiload_c2(B t, B w, B x) { for (usz i = 0; i < argn; i++) { BQNFFIEnt e = ffi_parseType(GetU(x,i+2), false); - u16 size; - if (isC32(e.o)) { // scalar - size = atomSize(e.o); - } else { - BQNFFIType* t = c(BQNFFIType, e.o); - if (t->ty==cty_ptr) size = sizeof(void*); // *any / &any - else if (t->ty==cty_struct) size = t->structSize; // {...} - else if (t->ty==cty_repr) { // any:any - B o2 = t->a[0].o; - if (isC32(o2)) size = atomSize(o2); - else { - if (c(BQNFFIType,o2)->ty != cty_ptr) thrM("FFI: Bad type with reinterpretation"); - size = sizeof(void*); - } - } else thrM("FFI: Unimplemented type"); - } - STATIC_ALLOC(e, size); + STATIC_ALLOC(e, calcStaticSize(e)); args[i+1] = e; (e.wholeArg? one : many)[e.extra] = true; @@ -888,7 +930,7 @@ B ffiload_c2(B t, B w, B x) { #define FFI_TYPE_FLDS(OBJ, PTR) \ BQNFFIType* t = (BQNFFIType*) x; \ BQNFFIEnt* arr=t->a; usz ia=t->ia; \ - if (t->ty == cty_struct) { \ + if (t->ty==cty_struct || t->ty==cty_starr) { \ ia--; \ if (arr[ia].structData!=NULL) { \ PTR(arr[ia].structData); \ @@ -906,7 +948,7 @@ void ffiType_print(FILE* f, B x) { BQNFFIType* t = c(BQNFFIType,x); fprintf(f, "cty_%d⟨", t->ty); usz ia = t->ia; - if (t->ty==cty_struct) ia--; + if (t->ty==cty_struct || t->ty==cty_starr) ia--; for (usz i=0; ia[i].o); diff --git a/test/ffi/ffiTest.c b/test/ffi/ffiTest.c index 6ad08611..f8c4cd24 100644 --- a/test/ffi/ffiTest.c +++ b/test/ffi/ffiTest.c @@ -250,3 +250,43 @@ uint32_t arrayRefOp(U32Arr* a) { int plusone(int x) { return x + 1; } + + + +int32_t arrarg(int32_t a[3]) { + return a[0] + a[1] + a[2]; +} + +typedef struct { + int32_t a[1]; +} ArrStruct1; + +int32_t arrstruct1(ArrStruct1 a, ArrStruct1* b) { + return a.a[0] + b->a[0]; +} + +int32_t ptrToArr(int32_t (*a)[2]) { + return a[3][0]+a[4][1]; +} + +typedef struct { + int32_t a[1]; + ArrStruct1 b[2]; + double c[3]; +} ArrStruct2; + +ArrStruct2 arrstruct2(ArrStruct2 a, ArrStruct2* b) { + return (ArrStruct2){ + .a = {a.a[0] + b->a[0]}, + .b = {{a.b[0].a[0] + b->b[1].a[0]}, {a.b[1].a[0] + b->b[0].a[0]}}, + .c = {1.0, 2.0, a.c[0] + a.c[2] + b->c[1] + b->c[2]} + }; +} +void arrstruct2Inc(ArrStruct2* b) { + for (int i = 0; i < 2; i++) { + b[i].a[0]++; + b[i].b[1].a[0]++; + b[i].c[1]++; + b[i].c[2]++; + } +} \ No newline at end of file diff --git a/test/ffi/test.bqn b/test/ffi/test.bqn index 4d32a7be..de89e788 100644 --- a/test/ffi/test.bqn +++ b/test/ffi/test.bqn @@ -90,11 +90,28 @@ f ↩ @ •FFI ⟨"i32", "putchar", ">i32"⟩ ⋄ F¨ 10∾˜"text"-@ •term.Flush@ f ↩ @ •FFI ⟨"a", "bqn_makeChar", ">u64"⟩ ⋄ •Show F 120169 +Section "# array types" + +f ↩ "lib.so"•FFI"i32"‿"arrarg"‿">[3]i32" ⋄ •Show F ⟨1,2,3⟩ +f ↩ "lib.so"•FFI"i32"‿"ptrToArr"‿">*[2]i32" ⋄ •Show F ⟨0‿0, 0‿0, 0‿0, 10‿20, 3‿4⟩ + +as1 ← "{[1]i32}" +f ↩ "lib.so"•FFI ⟨"i32", "arrstruct1", as1, "*"∾as1⟩ ⋄ •Show F ⟨⋈⋈10, ⟨⋈⋈20⟩⟩ + +as2 ← "{[1]i32,[2]"∾as1∾",[3]f64}" +f ↩ "lib.so"•FFI ⟨as2, "arrstruct2", as2, "*"∾as2⟩ ⋄ •Out •Repr F ⟨⟨⟨4⟩ ⋄ ⋈∘⋈¨1‿2 ⋄ 1.2‿2.3‿3.4⟩, ⟨⟨⟨5⟩ ⋄ ⋈∘⋈¨10‿20 ⋄ 5.1‿6.1‿7.1⟩⟩⟩ +f ↩ "lib.so"•FFI ⟨"&", "arrstruct2Inc", ">&"∾as2⟩ ⋄ •Out •Repr F (⊢⋈50⊸+) ⟨⟨10⟩ ⋄ ⋈∘⋈¨11‿12 ⋄ 13‿14‿15⟩ + # erroring: -# "local/lib.so" •FFI ""‿"printArgs"‿"i8"‿"i16:c32"‿"i32"‿"u8"‿"u16"‿"u32"‿"f32"‿"f64" -# "local/lib.so" •FFI ""‿"testArgs"‿"i8:c16"‿"i16"‿"i32"‿"u8"‿"u16"‿"u32"‿"f32"‿"f64" -# "local/lib.so" •FFI ""‿"testArgs"‿"i8:c32"‿"i16"‿"i32"‿"u8"‿"u16"‿"u32"‿"f32"‿"f64" +# "lib.so" •FFI ""‿"printArgs"‿"i8"‿"i16:c32"‿"i32"‿"u8"‿"u16"‿"u32"‿"f32"‿"f64" +# "lib.so" •FFI ""‿"testArgs"‿"i8:c16"‿"i16"‿"i32"‿"u8"‿"u16"‿"u32"‿"f32"‿"f64" +# "lib.so" •FFI ""‿"testArgs"‿"i8:c32"‿"i16"‿"i32"‿"u8"‿"u16"‿"u32"‿"f32"‿"f64" -# f ↩ "local/lib.so" •FFI "u64"‿"ident_u64"‿">u64:i32" ⋄ •Show F 1234‿12344444 -# f ↩ "local/lib.so" •FFI "u64"‿"ident_u64"‿">u64" ⋄ •Show F +´2⋆53‿20 \ No newline at end of file +# f ↩ "lib.so" •FFI "u64"‿"ident_u64"‿">u64:i32" ⋄ •Show F 1234‿12344444 +# f ↩ "lib.so" •FFI "u64"‿"ident_u64"‿">u64" ⋄ •Show F +´2⋆53‿20 + +# f ↩ "lib.so"•FFI"i32"‿"arrarg"‿">[3]i32" ⋄ •Show F ⟨1,2⟩ +# f ↩ "lib.so"•FFI ⟨as2, "arrstruct2", as2, "*"∾as2⟩ ⋄ •Show F ⟨⟨⟨4⟩ ⋄ ⋈∘⋈¨1‿2 ⋄ 1.2‿2.3‿3.4‿4⟩, ⟨⟨⟨5⟩ ⋄ ⋈∘⋈¨10‿20 ⋄ 5.1‿6.1‿7.1⟩⟩⟩ +# f ↩ "lib.so"•FFI ⟨as2, "arrstruct2", as2, "*"∾as2⟩ ⋄ •Show F ⟨⟨⟨4⟩ ⋄ ⋈∘⋈¨1‿2 ⋄ 1.2‿2.3‿3.4⟩, ⟨⟨⟨5⟩ ⋄ ⋈∘⋈¨10‿20 ⋄ 5.1‿6.1‿7.1‿5⟩⟩⟩ +# f ↩ "lib.so"•FFI ⟨as2, "arrstruct2", as2, "*"∾as2⟩ ⋄ •Show F ⟨⟨⟨4⟩ ⋄ ⋈∘⋈¨1‿2 ⋄ 1.2‿2.3‿3.4⟩, ⟨⟨⟨5⟩ ⋄ ⋈∘⋈¨10‿20 ⋄ 5.1‿6.1‿7.1,5⟩⟩⟩ \ No newline at end of file diff --git a/test/ffi/test.expected b/test/ffi/test.expected index 8207b92c..624ea0ec 100644 --- a/test/ffi/test.expected +++ b/test/ffi/test.expected @@ -115,3 +115,10 @@ ff7fdfefefdf7bb4 ff7fdfefefdf7bb4 fefffdfff7ffbffb bffff7fffdfffeff # self-ffi text '𝕩' + +# array types +6 +14 +30 +⟨⟨9⟩,⟨⟨⟨21⟩⟩,⟨⟨12⟩⟩⟩,1‿2‿17.799999999999997⟩ +⟨⟨⟨11⟩,⟨⟨⟨11⟩⟩,⟨⟨13⟩⟩⟩,13‿15‿16⟩,⟨⟨61⟩,⟨⟨⟨61⟩⟩,⟨⟨63⟩⟩⟩,63‿65‿66⟩⟩