From 1454ca3a1a59324bd3fca6a48c98d8b5d3ed2425 Mon Sep 17 00:00:00 2001 From: dzaima Date: Fri, 28 Jul 2023 13:59:25 +0300 Subject: [PATCH] expand & test FFI error checking --- src/builtins/sysfn.c | 4 +- src/ffi.c | 159 ++++++++++++++++++++++++++++++----------- src/utils/calls.h | 4 +- test/cases/ffi.bqn | 143 ++++++++++++++++++++++++++++++++++++ test/cases/perf.bqn | 4 +- test/cases/prims.bqn | 4 +- test/cases/system.bqn | 4 +- test/ffi/ffiTest.c | 1 + test/ffi/test.bqn | 9 ++- test/ffi/test.expected | 6 +- test/run.bqn | 2 +- 11 files changed, 283 insertions(+), 57 deletions(-) create mode 100644 test/cases/ffi.bqn diff --git a/src/builtins/sysfn.c b/src/builtins/sysfn.c index 1eb9b714..0daf86ff 100644 --- a/src/builtins/sysfn.c +++ b/src/builtins/sysfn.c @@ -284,8 +284,8 @@ static B args_path(B* fullpath, B w, char* name) { // consumes w, returns args, if (!isArr(w) || RNK(w)!=1 || IA(w)>3) thrF("%U: 𝕨 must be a vector with at most 3 items, but had shape %H", name, w); usz ia = IA(w); SGet(w) - B path = ia>0? vfyStr(Get(w,0),name,"path" ) : inc(cdPath); - B file = ia>1? vfyStr(Get(w,1),name,"filename") : emptyCVec(); + B path = ia>0? vfyStr(Get(w,0),name,"Path" ) : inc(cdPath); + B file = ia>1? vfyStr(Get(w,1),name,"Filename") : emptyCVec(); B args = ia>2? Get(w,2) : emptySVec(); *fullpath = vec_join(vec_addN(path, m_c32('/')), file); decG(w); diff --git a/src/ffi.c b/src/ffi.c index 7fa4b12c..0c587ea6 100644 --- a/src/ffi.c +++ b/src/ffi.c @@ -4,6 +4,10 @@ #error "Expected CBQN_EXPORT if FFI is defined" #endif +#ifndef FFI_CHECKS // option to disable extended correctness checks + #define FFI_CHECKS 1 +#endif + #if CBQN_EXPORT #if defined(_WIN32) || defined(_WIN64) #define BQN_EXP __attribute__((__visibility__("default"))) __declspec(dllexport) @@ -11,7 +15,7 @@ #define BQN_EXP __attribute__((__visibility__("default"))) #endif #include "../include/bqnffi.h" - #include "utils/utf.h" + #include "utils/calls.h" #include "utils/cstr.h" #include "nfns.h" #include "ns.h" @@ -47,8 +51,11 @@ BQN_EXP void bqn_init() { } B type_c1(B t, B x); +static i32 typeInt(B x) { // doesn't consume + return o2i(C1(type, inc(x))); +} BQN_EXP int bqn_type(BQNV v) { - return o2i(type_c1(bi_N, inc(getB(v)))); + return typeInt(getB(v)); } BQN_EXP BQNV bqn_call1(BQNV f, BQNV x) { @@ -156,6 +163,8 @@ typedef struct BoundFn { void* w_c2; #if FFI==2 i32 mutCount; + i32 wLen; // 0: not needed; -1: is whole array; ≥1: length + i32 xLen; // 0: length 0 array; else, ↑ #endif } BoundFn; NFnDesc* boundFnDesc; @@ -310,7 +319,9 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty bool parseRepr=false, canRetype=false, mut=false; u32 myWidth = 0; // used if parseRepr switch (c0) { - default: thrF("FFI: Error parsing type: Unexpected character '%c'", c0); + default: + if (c0 == '\0') thrF("FFI: Error parsing type: Unexpected end of input"); + thrF("FFI: Error parsing type: Unexpected character '%c'", c0); case '[': { u32 n = readUInt(&c); if (*c++!=']') thrM("FFI: Bad array type"); @@ -374,7 +385,7 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty case 'a': { ro = m_c32(sty_a); - assert(sizeof(BQNV)==8); // ffi_type_uint64 must be the same as BQNV + assert(sizeof(BQNV)==8); // if changed, change the below ffi_type_uint64 too rt = ffi_type_uint64; break; } @@ -405,7 +416,7 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty if (e.mutates) thrM("FFI: Structs currently cannot contain mutable references"); u32 m = *c++; if (m=='}') break; - if (m!=',') thrM("FFI: Invalid struct member"); + if (m!=',') thrM("FFI: Improper struct separator or end"); } usz n = TSSIZE(es); BQNFFIEnt* rp; ro = m_bqnFFIType(&rp, cty_struct, n+1); @@ -433,8 +444,8 @@ BQNFFIEnt ffi_parseTypeStr(u32** src, bool inPtr, bool top) { // parse actual ty c++; u8 t = *c++; u32 n = readUInt(&c); - if (t=='i' | t=='c') if (n!=8 & n!=16 & n!=32) { badW: thrF("Bad width in :%c%i", (u32)t, n); } - if (t=='u') if (n!=1 & n!=8 & n!=16 & n!=32) goto badW; + if (t=='i' | t=='c') if (n!=8 & n!=16 & n!=32) { badW: thrF("Unsupported width in :%c%i", (u32)t, n); } + if (t=='u') if (n!=1) goto badW; if (t=='f') if (n!=64) goto badW; if (isC32(ro) && n > myWidth*8) thrF("FFI: Representation wider than the value for \"%S:%c%i\"", sty_names[o2cG(ro)], (u32)t, n); @@ -472,7 +483,7 @@ BQNFFIEnt ffi_parseType(B arg, bool forRes) { // doesn't consume; parse argument while (true) { if (*xp == U'𝕩') { if (side) thrM("FFI: Multiple occurrences of argument side specified"); side = 1; } else if (*xp == U'𝕨') { if (side) thrM("FFI: Multiple occurrences of argument side specified"); side = 2; } - else if (*xp == U'>') { if (whole) thrM("FFI: Multiple occurrences of '>'"); whole = true; } + else if (*xp == U'>') { if (whole) thrM("FFI: Multiple occurrences of '>' within one argument"); whole = true; } else break; xp++; } @@ -482,7 +493,7 @@ BQNFFIEnt ffi_parseType(B arg, bool forRes) { // doesn't consume; parse argument t = ffi_parseTypeStr(&xp, false, true); // printI(arg); printf(": "); printFFIType(stdout, t.o); printf("\n"); - if (xp!=xpN) thrM("FFI: Bad type descriptor"); + if (xp!=xpN) thrM("FFI: Garbage at end of type"); t.onW = side; // keep .mutates t.wholeArg = whole; @@ -523,6 +534,48 @@ static B makeRe(u8 reT, u8 reW/*log*/, u8* src, u32 elW/*bytes*/) { FORCE_INLINE u64 i64abs(i64 x) { return x<0?-x:x; } +#if FFI_CHECKS + static char* typeDesc(i32 typeNum) { + switch(typeNum) { + default: return "(unknown)"; + case 0: return "an array"; + case 1: return "a number"; + case 2: return "a character"; + case 3: return "a function"; + case 4: return "a 1-modifier"; + case 5: return "a 2-modifier"; + case 6: return "a namespace"; + } + } + static NOINLINE i32 nonNumber(B x) { // returns -1 if x is all numeric, otherwise •Type of the offending element + if (elNum(TI(x,elType))) return -1; + usz ia = IA(x); SGetU(x) + i32 r = -1; + for (ux i = 0; i < ia; i++) { + B c = GetU(x,i); + if (!isNum(c)) { r = typeInt(c); break; } + } + return r; + } + static NOINLINE void ffi_checkRange(B x, bool mut, char* desc, i64 min, i64 max) { // doesn't consume; assumes non-array has already been checked for; if min==max, doesn't check range + if (IA(x)==0) return; + + char ref = mut? '&' : '*'; + i32 nonNum = nonNumber(x); + if (nonNum!=-1) thrF("FFI: Array provided for %c%S contained %S", ref, desc, typeDesc(nonNum)); + + if (min==max) return; + incG(x); x = elNum(TI(x,elType))? x : taga(cpyF64Arr(x)); + + i64 buf[2]; + if (!getRange_fns[TI(x,elType)](tyany_ptr(x), buf, IA(x))) thrF("Array provided for %c%S contained non-integer", ref, desc); + if (buf[0]max) thrF("FFI: Array provided for %c%S contained %l", ref, desc, buf[1]); + decG(x); + } +#else + static void ffi_checkRange(B x, bool mut, char* desc, i64 min, i64 max) { } +#endif #define CPY_UNSIGNED(REL, UEL, DIRECT, WIDEN, WEL) \ if (TI(x,elType)<=el_##REL) return taga(DIRECT(x)); \ @@ -559,14 +612,14 @@ void genObj(B o, B c, bool anyMut, void* ptr) { switch(t) { default: UD; // thrF("FFI: Unimplemented scalar type \"%S\"", sty_names[t]); case sty_a: *(BQNV*)ptr = makeX(inc(c)); break; case sty_ptr: thrM("FFI: \"*\" unimplemented"); break; - case sty_u8: if(f!=( u8)f) thrM("FFI: u8 argument not exact" ); *( u8*)ptr = f; break; - case sty_i8: if(f!=( i8)f) thrM("FFI: i8 argument not exact" ); *( i8*)ptr = f; break; - case sty_u16: if(f!=(u16)f) thrM("FFI: u16 argument not exact"); *(u16*)ptr = f; break; - case sty_i16: if(f!=(i16)f) thrM("FFI: i16 argument not exact"); *(i16*)ptr = f; break; - case sty_u32: if(f!=(u32)f) thrM("FFI: u32 argument not exact"); *(u32*)ptr = f; break; - case sty_i32: if(f!=(i32)f) thrM("FFI: i32 argument not exact"); *(i32*)ptr = f; break; - case sty_u64: if(f!=(u64)f) thrM("FFI: u64 argument not exact"); if ( (u64)f >= (1ULL<<53)) thrM("FFI: u64 argument value ≥ 2⋆53"); *(u64*)ptr = f; break; - case sty_i64: if(f!=(i64)f) thrM("FFI: i64 argument not exact"); if ((u64)((1ULL<<53)+(u64)f) >= (2ULL<<53)) thrM("FFI: i64 argument absolute value ≥ 2⋆53"); *(i64*)ptr = f; break; + case sty_u8: { u8 i = ( u8)f; if(f!=i) thrM("FFI: u8 argument not exact" ); *( u8*)ptr = i; break; } + case sty_i8: { i8 i = ( i8)f; if(f!=i) thrM("FFI: i8 argument not exact" ); *( i8*)ptr = i; break; } + case sty_u16: { u16 i = (u16)f; if(f!=i) thrM("FFI: u16 argument not exact"); *(u16*)ptr = i; break; } + case sty_i16: { i16 i = (i16)f; if(f!=i) thrM("FFI: i16 argument not exact"); *(i16*)ptr = i; break; } + case sty_u32: { u32 i = (u32)f; if(f!=i) thrM("FFI: u32 argument not exact"); *(u32*)ptr = i; break; } + case sty_i32: { i32 i = (i32)f; if(f!=i) thrM("FFI: i32 argument not exact"); *(i32*)ptr = i; break; } + case sty_u64: { u64 i = (u64)f; if(f!=i) thrM("FFI: u64 argument not exact"); if (i>=(1ULL<<53)) thrM("FFI: u64 argument value ≥ 2⋆53"); *(u64*)ptr = i; break; } + case sty_i64: { i64 i = (i64)f; if(f!=i) thrM("FFI: i64 argument not exact"); if (i>=(1LL<<53) || i<=-(1LL<<53)) thrM("FFI: i64 argument absolute value ≥ 2⋆53"); *(i64*)ptr = i; break; } case sty_f32: *(float* )ptr = f; break; case sty_f64: *(double*)ptr = f; break; } @@ -585,22 +638,22 @@ void genObj(B o, B c, bool anyMut, void* ptr) { incG(c); B cG; 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; - case sty_i16: cG = mut? taga(cpyI16Arr(c)) : toI16Any(c); break; - case sty_i32: cG = mut? taga(cpyI32Arr(c)) : toI32Any(c); break; - case sty_f64: cG = mut? taga(cpyF64Arr(c)) : toF64Any(c); break; - case sty_u8: cG = mut? cpyU8Bits (c) : toU8Bits (c); break; - case sty_u16: cG = mut? cpyU16Bits(c) : toU16Bits(c); break; - case sty_u32: cG = mut? cpyU32Bits(c) : toU32Bits(c); break; - case sty_f32: cG = cpyF32Bits(c); break; // no direct f32 type, so no direct reference option + switch(o2cG(e)) { default: thrF("FFI: \"*%S\" argument type not yet implemented", sty_names[o2cG(e)]); + case sty_i8: ffi_checkRange(c, mut, "i8", I8_MIN, I8_MAX); cG = mut? taga(cpyI8Arr (c)) : toI8Any (c); break; + case sty_i16: ffi_checkRange(c, mut, "i16", I16_MIN, I16_MAX); cG = mut? taga(cpyI16Arr(c)) : toI16Any(c); break; + case sty_i32: ffi_checkRange(c, mut, "i32", I32_MIN, I32_MAX); cG = mut? taga(cpyI32Arr(c)) : toI32Any(c); break; + case sty_f64: ffi_checkRange(c, mut, "f64", 0, 0); cG = mut? taga(cpyF64Arr(c)) : toF64Any(c); break; + case sty_u8: ffi_checkRange(c, mut, "u8", 0, U8_MAX); cG = mut? cpyU8Bits (c) : toU8Bits (c); break; + case sty_u16: ffi_checkRange(c, mut, "u16", 0, U16_MAX); cG = mut? cpyU16Bits(c) : toU16Bits(c); break; + case sty_u32: ffi_checkRange(c, mut, "u32", 0, U32_MAX); cG = mut? cpyU32Bits(c) : toU32Bits(c); break; + case sty_f32: ffi_checkRange(c, mut, "f64", 0, 0); cG = cpyF32Bits(c); break; // no direct f32 type, so no direct reference option } ffiObjs = vec_addN(ffiObjs, cG); *(void**)ptr = tyany_ptr(cG); } else { // *{...} / &{...} / *[n]any BQNFFIType* t2 = c(BQNFFIType, e); - if (t2->ty!=cty_struct && t2->ty!=cty_starr) thrM("FFI: Unimplemented pointer element type"); + if (t2->ty!=cty_struct && t2->ty!=cty_starr) thrM("FFI: Pointer element type not implemented"); usz elSz = t2->structSize; @@ -620,7 +673,7 @@ void genObj(B o, B c, bool anyMut, void* ptr) { u8 et = o2cG(o2); u8 etw = sty_w[et]*8; if (!isArr(c)) thrF("FFI: Expected array corresponding to \"%S:%c%i\"", sty_names[et], (u32)reT, 1<>reW) thrM("FFI: Bad input array length"); + if (IA(c) != etw>>reW) thrF("FFI: Bad array corresponding to \"%S:%c%i\": expected %s elements, got %s", sty_names[et], (u32)reT, 1<>reW), IA(c)); B cG = toW(reT, reW, incG(c)); memcpy(ptr, tyany_ptr(cG), 8); // may over-read, ¯\_(ツ)_/¯ dec(cG); @@ -674,7 +727,7 @@ B readSimple(u8 resCType, u8* ptr) { B r; switch(resCType) { default: UD; // thrM("FFI: Unimplemented type"); case sty_void: r = m_c32(0); break; - case sty_ptr: thrM("FFI: \"*\" unimplemented"); break; + case sty_ptr: thrM("FFI: \"*\" not yet implemented"); break; case sty_a: r = getB(*(BQNV*)ptr); break; case sty_i8: r = m_i32(*( i8*)ptr); break; case sty_u8: r = m_i32(*( u8*)ptr); break; case sty_i16: r = m_i32(*(i16*)ptr); break; case sty_u16: r = m_i32(*(u16*)ptr); break; @@ -757,12 +810,21 @@ B libffiFn_c2(B t, B w, B x) { BoundFn* bf = c(BoundFn,t); B argObj = c(HArr,bf->obj)->a[0]; - u32 flags = ptr2u64(bf->w_c1); + #define PROC_ARG(L, U, S, NG) \ + Arr* L##a ONLY_GCC(=0); \ + AS2B L##f ONLY_GCC(=0); \ + if (bf->L##Len>0) { \ + if (FFI_CHECKS) { \ + if (!isArr(L)) thrM("FFI: Expected array " S); \ + if (bf->L##Len>0 && IA(L)!=bf->L##Len) thrF("FFI: Wrong argument count in " S ": expected %s, got %s", bf->L##Len, IA(L)); \ + } \ + L##a = a(L); \ + L##f = TIv(L##a,getU); \ + } else { NG } + + PROC_ARG(w, W, "𝕨", if (FFI_CHECKS && bf->wLen==0 && !q_N(w)) thrM("FFI: Unnecessary 𝕨 given");) + PROC_ARG(x, X, "𝕩", ) - Arr* wa ONLY_GCC(=0); AS2B wf ONLY_GCC(=0); - Arr* xa ONLY_GCC(=0); AS2B xf ONLY_GCC(=0); - if (flags&1) { wa=a(w); wf=TIv(wa,getU); } - if (flags&2) { xa=a(x); xf=TIv(xa,getU); } i32 idxs[2] = {0,0}; u8* tmpAlloc = TALLOCP(u8, c(BQNFFIType,argObj)->staticAllocTotal); @@ -775,7 +837,12 @@ B libffiFn_c2(B t, B w, B x) { BQNFFIEnt* ents = c(BQNFFIType,argObj)->a; for (usz i = 0; i < argn; i++) { BQNFFIEnt e = ents[i+1]; - B o = e.wholeArg? (e.onW? w : x) : (e.onW? wf : xf)(e.onW?wa:xa, idxs[e.onW]++); + B o; + if (e.wholeArg) { + o = e.onW? w : x; + } else { + o = (e.onW? wf : xf)(e.onW?wa:xa, idxs[e.onW]++); + } genObj(e.o, o, e.extra2, tmpAlloc + e.staticOffset); } @@ -811,6 +878,7 @@ B libffiFn_c2(B t, B w, B x) { i32 mutArgs = bf->mutCount; if (mutArgs) { usz objPos = 0; + u32 flags = ptr2u64(bf->w_c1); bool resSingle = flags&4; if (resSingle) { for (usz i = 0; i < argn; i++) { @@ -868,9 +936,10 @@ B ffiload_c2(B t, B w, B x) { usz xia = IA(x); if (xia<2) thrM("FFI: Function specification must have at least two items"); usz argn = xia-2; + if (argn >= U16_MAX) thrM("FFI: Too many arguments"); SGetU(x) B name = GetU(x,1); - vfyStr(name, "FFI", "type"); + vfyStr(name, "FFI", "Type"); u64 staticAlloc = ffiTmpAlign(argn * sizeof(void*)); // initial alloc is the argument pointer list #define STATIC_ALLOC(O, SZ) ({ (O).staticOffset = staticAlloc; staticAlloc+= ffiTmpAlign(SZ); }) @@ -899,8 +968,8 @@ B ffiload_c2(B t, B w, B x) { #if FFI==2 BQNFFIEnt* args; B argObj = m_bqnFFIType(&args, 255, argn+1); args[0] = tRes; - bool one [2]={0,0}; - bool many[2]={0,0}; + bool whole[2]={0,0}; + i32 count[2]={0,0}; i32 mutCount = 0; for (usz i = 0; i < argn; i++) { BQNFFIEnt e = ffi_parseType(GetU(x,i+2), false); @@ -908,19 +977,21 @@ B ffiload_c2(B t, B w, B x) { STATIC_ALLOC(e, calcStaticSize(e)); args[i+1] = e; - (e.wholeArg? one : many)[e.extra] = true; + if (e.wholeArg) whole[e.onW] = true; + count[e.onW]++; if (e.mutates) mutCount++; } if (staticAlloc > U16_MAX-64) thrM("FFI: Static argument size too large"); c(BQNFFIType,argObj)->staticAllocTotal = ffiTmpAlign(staticAlloc); - if (one[0] && many[0]) thrM("FFI: Multiple arguments for 𝕩 specified, but one has '>'"); - if (one[1] && many[1]) thrM("FFI: Multiple arguments for 𝕨 specified, but one has '>'"); + if (count[0]>1 && whole[0]) thrM("FFI: Multiple arguments on 𝕩 specified, some with '>'"); + if (count[1]>1 && whole[1]) thrM("FFI: Multiple arguments on 𝕨 specified, some with '>'"); + if (count[0]==0 && count[1]>0) thrM("FFI: At least one argument should be in 𝕩"); #else i32 mutCount = 0; for (usz i = 0; i < argn; i++) ffi_parseType(GetU(x,i+2), false); (void)tRes; #endif - if (tRes.resSingle && mutCount!=1) thrF("FFI: Return was \"&\", but found %i mutated values", mutCount); + if (tRes.resSingle && mutCount!=1) thrF("FFI: Return value is specified as \"&\", but there are %i mutated values", mutCount); char* ws = NULL; if (w.u != m_c32(0).u) { @@ -955,9 +1026,11 @@ B ffiload_c2(B t, B w, B x) { ffi_status s = ffi_prep_cif((ffi_cif*)cif->data, FFI_DEFAULT_ABI, argn, &args[0].t, argsRawArr); if (s!=FFI_OK) thrM("FFI: Error preparing call interface"); // mm_free(argsRaw) - u32 flags = many[1] | many[0]<<1 | tRes.resSingle<<2; + u32 flags = tRes.resSingle<<2; B r = m_ffiFn(foreignFnDesc, m_hvec3(argObj, tag(cif, OBJ_TAG), tag(argsRaw, OBJ_TAG)), libffiFn_c1, libffiFn_c2, TOPTR(void,flags), sym); c(BoundFn,r)->mutCount = mutCount; + c(BoundFn,r)->wLen = whole[1]? -1 : count[1]; + c(BoundFn,r)->xLen = whole[0]? -1 : count[0]; return r; #endif } diff --git a/src/utils/calls.h b/src/utils/calls.h index 72064802..1fcd77ab 100644 --- a/src/utils/calls.h +++ b/src/utils/calls.h @@ -35,5 +35,5 @@ typedef struct { EqFn fn; u8 data; } EqFnObj; #define EQFN_GET(W_ELT, X_ELT) ({ u8 eqfn_i_ = EQFN_INDEX(W_ELT, X_ELT); (EqFnObj){.fn=eqFns[eqfn_i_], .data=eqFnData[eqfn_i_]}; }) #define EQFN_CALL(FN, W, X, L) (FN).fn(W, X, L, (FN).data) -typedef bool (*RangeFn)(void* xp, i64* res, u64 len); // writes min,max in res; returns 0 and leaves res undefined if either any (floor(x)≠x or abs>2⋆53), or (x≠(i64)x) -extern RangeFn getRange_fns[el_f64+1]; +typedef bool (*RangeFn)(void* xp, i64* res, u64 len); // writes min,max in res, assumes len≥1; returns 0 and leaves res undefined if either any (floor(x)≠x or abs>2⋆53), or (x≠(i64)x) +extern RangeFn getRange_fns[el_f64+1]; // limited to ≤el_f64 diff --git a/test/cases/ffi.bqn b/test/cases/ffi.bqn new file mode 100644 index 00000000..84d16685 --- /dev/null +++ b/test/cases/ffi.bqn @@ -0,0 +1,143 @@ +%DEF var V←•internal.Variation ⋄ LV←•internal.ListVariations ⋄ CLR←•internal.ClearRefs +%DEF tvar %USE var ⋄ _tvar ← {F _𝕣 x: (CLR@) ⊢ {F 𝕩 V x}¨ LV 𝕩; w F _𝕣 x: (CLR@) ⊢ (LV 𝕨) {(𝕨 V w) F 𝕩 V x}⌜ LV 𝕩} +%DEF defs size_t ← "u64" + + +# bad •FFI invocation +# generally weird +@ •FFI⎊9 ""‿"bqn_this symbol doesn't exist" %% 9 +!"FFI: Type must be a string" % @•FFI "hello" +!"FFI: Error parsing type: Unexpected character '?'" % @•FFI""‿"bqn_init"‿"?" +!"FFI: Bad array type" % @•FFI""‿"bqn_init"‿"[" +!"FFI: Bad array type" % @•FFI""‿"bqn_init"‿"[0" +!"FFI: Error parsing type: Unexpected end of input" % @•FFI""‿"bqn_init"‿"[1]" +!"FFI: Error parsing type: Unexpected end of input" % @•FFI""‿"bqn_init"‿"{" +!"FFI: Bad integer width" % @•FFI""‿"bqn_init"‿"i" +!"FFI: Bad float width" % @•FFI""‿"bqn_init"‿"f80" +!"FFI: Bad float width" % @•FFI""‿"bqn_init"‿"f128" +!"FFI: Bad float width" % @•FFI""‿"bqn_init"‿"f16" +!"FFI: Bad float width" % @•FFI""‿"bqn_init"‿"f1" +!"FFI: Bad integer width" % @•FFI""‿"bqn_init"‿"u1" +!"FFI: Bad integer width" % @•FFI""‿"bqn_init"‿"i0" +!"FFI: Bad integer width" % @•FFI""‿"bqn_init"‿"i10" +!"FFI: Bad integer width" % @•FFI""‿"bqn_init"‿"u128" +!"FFI: number literal too large" % @•FFI""‿"bqn_init"‿"u99999999999999" +!"FFI: Too many arguments" % @•FFI""‿"bqn_init"∾70000⥊<"i32" + +# >/𝕨/𝕩 +!"FFI: At least one argument should be in 𝕩" % @•FFI""‿"bqn_init"‿"𝕨i32"‿"𝕨i32" +!"FFI: Multiple occurrences of '>' within one argument" % @•FFI""‿"bqn_init"‿">>i32" +!"FFI: Multiple occurrences of argument side specified" % @•FFI""‿"bqn_init"‿"𝕨𝕨i32" +!"FFI: Multiple occurrences of argument side specified" % @•FFI""‿"bqn_init"‿"𝕩𝕩i32" +!"FFI: Multiple occurrences of argument side specified" % @•FFI""‿"bqn_init"‿"𝕨𝕩i32" +!"FFI: Multiple arguments on 𝕩 specified, some with '>'" % @•FFI""‿"bqn_init"‿">i32"‿">i32" +!"FFI: Multiple arguments on 𝕨 specified, some with '>'" % @•FFI""‿"bqn_init"‿">𝕨i32"‿">𝕨i32" +!"FFI: Multiple arguments on 𝕩 specified, some with '>'" % @•FFI""‿"bqn_init"‿">i32"‿"i32" +!"FFI: Multiple arguments on 𝕨 specified, some with '>'" % @•FFI""‿"bqn_init"‿">𝕨i32"‿"𝕨i32" + +# arrays +!"FFI: 0-item arrays not supported" % @•FFI""‿"bqn_init"‿"[0]i32" +!"FFI: number literal too large" % @•FFI""‿"bqn_init"‿"[999999999999]i32" +!"FFI: Top-level array too large; limit is 65535 elements" % @•FFI""‿"bqn_init"‿"[65536]i32" +!"FFI: Top-level array too large; limit is 65535 elements" % @•FFI""‿"bqn_init"‿"[4000000000]i32" +!"FFI: Array too large; limit is 65534 bytes" % @•FFI""‿"bqn_init"‿"*[20000]i32" +!"FFI: Cannot return array" % @•FFI"[4]i32"‿"bqn_init" + +# structs +!"FFI: Improper struct separator or end" % @•FFI""‿"bqn_init"‿"{*i32*i32}" +!"FFI: Improper struct separator or end" % @•FFI""‿"bqn_init"‿"{*i32" +!"FFI: Error parsing type: Unexpected end of input" % @•FFI""‿"bqn_init"‿"{*i32," +!"FFI: Structs currently cannot contain mutable references" % @•FFI""‿"bqn_init"‿"{&i32}" + +# : +!"FFI: Garbage at end of type" % @•FFI""‿"bqn_init"‿"i32:" +!"Unsupported width in :i0" % @•FFI""‿"bqn_init"‿"i32:i" +!"Unsupported width in :i9" % @•FFI""‿"bqn_init"‿"i32:i9" +!"Unsupported width in :u16" % @•FFI""‿"bqn_init"‿"i32:u16" +!"Unsupported width in :i64" % @•FFI"i32:i64"‿"bqn_init" +!"Unsupported width in :f32" % @•FFI""‿"bqn_init"‿"i32:f32" +!"FFI: number literal too large" % @•FFI""‿"bqn_init"‿"i32:u9999999999999999" +!"FFI: Representation wider than the value for ""i32:f64""" % @•FFI""‿"bqn_init"‿"i32:f64" +!"FFI: Garbage at end of type" % @•FFI""‿"bqn_init"‿"i32 hello world" + +# return value +!"FFI: Function specification must have at least two items" % @•FFI ⟨"&"⟩ +!"FFI: Return value is specified as ""&"", but there are 0 mutated values" % @•FFI"&"‿"bqn_init"‿">*i32" +!"FFI: Return value is specified as ""&"", but there are 2 mutated values" % @•FFI"&"‿"bqn_init"‿"&i32"‿"&i32" +!"FFI: Cannot return array" % @•FFI"[4]i32"‿"bqn_init" +!"FFI: Unimplemented result type" % @•FFI"*i32"‿"bqn_init" +!"FFI: Unimplemented result type" % @•FFI"&i32"‿"bqn_init" + + +# correct elements +%USE defs ⋄ f←@•FFI"&"‿"memcpy"‿"&u8"‿"*i8"‿size_t ⋄ G←{F⟨𝕩,↕0,0⟩} ⋄ %USE tvar ⋄ !∘≡¨⟜⊏ G _tvar 100⥊1‿0 +%USE defs ⋄ f←@•FFI"&"‿"memcpy"‿"&u8"‿"*i8"‿size_t ⋄ G←{F⟨𝕩,↕0,0⟩} ⋄ G ↕200 %% ↕200 +%USE defs ⋄ f←@•FFI"&"‿"memcpy"‿"&u16"‿"*i8"‿size_t ⋄ G←{F⟨𝕩,↕0,0⟩} ⋄ G ⋈65535 %% ⋈65535 +%USE defs ⋄ f←@•FFI"&"‿"memcpy"‿"&i16"‿"*i8"‿size_t ⋄ G←{F⟨𝕩,↕0,0⟩} ⋄ G ¯32768‿32767 %% ¯32768‿32767 +%USE defs ⋄ f←@•FFI"&"‿"memcpy"‿"&{i16}"‿"*i8"‿size_t ⋄ G←{F⟨𝕩,↕0,0⟩} ⋄ G ⋈¨¯32768‿32767 %% ⋈¨¯32768‿32767 + + + +# bad array elements +!"FFI: Structs currently cannot contain mutable references" % %USE defs ⋄ f←@•FFI"&"‿"memcpy"‿"&{&i16}"‿"*i8"‿size_t ⋄ G←{F⟨𝕩,↕0,0⟩} ⋄ G ⋈∘⋈¨¯32768‿32767 + +!"FFI: Array provided for &u8 contained 299" % f←@•FFI"&"‿"bqn_init"‿">&u8" ⋄ F ↕300 +!"FFI: Array provided for &u8 contained ¯99" % f←@•FFI"&"‿"bqn_init"‿">&u8" ⋄ F -↕100 +!"FFI: Array provided for &u16 contained 65536" % f←@•FFI"&"‿"bqn_init"‿">&u16" ⋄ F ⋈65536 +!"FFI: Array provided for &u32 contained 4294967296" % f←@•FFI"&"‿"bqn_init"‿">&u32" ⋄ F ⋈2⋆32 +!"FFI: Array provided for &u16 contained ¯1" % f←@•FFI"&"‿"bqn_init"‿">&u16" ⋄ F ⋈¯1 +!"FFI: Array provided for &u32 contained ¯1" % f←@•FFI"&"‿"bqn_init"‿">&u32" ⋄ F ⋈¯1 + +!"FFI: Array provided for &i16 contained 32768" % f←@•FFI"&"‿"bqn_init"‿">&i16" ⋄ F ¯32768‿32768 +!"FFI: Array provided for &i16 contained ¯32769" % f←@•FFI"&"‿"bqn_init"‿">&i16" ⋄ F ¯32768‿¯32769 + +!"FFI: Array provided for &u8 contained an array" % f←@•FFI"&"‿"bqn_init"‿">&u8" ⋄ F ⟨↕300⟩ +!"FFI: Array provided for &i16 contained a character" % f←@•FFI"&"‿"bqn_init"‿">&i16" ⋄ F "hi" +!"FFI: Array provided for &f64 contained a namespace" % f←@•FFI"&"‿"bqn_init"‿">&f64" ⋄ F 1‿2‿{⇐} + +# bad scalars +!"FFI: u8 argument not exact" % f←@•FFI""‿"bqn_init"‿">u8" ⋄ F 256 +!"FFI: u16 argument not exact" % f←@•FFI""‿"bqn_init"‿">u16" ⋄ F ¯1 +!"FFI: u32 argument not exact" % f←@•FFI""‿"bqn_init"‿">u32" ⋄ F ¯1 +!"FFI: u64 argument not exact" % f←@•FFI""‿"bqn_init"‿">u64" ⋄ F ¯1 +!"FFI: u32 argument not exact" % f←@•FFI""‿"bqn_init"‿">u32" ⋄ F 2⋆32 +!"FFI: u32 argument not exact" % f←@•FFI""‿"bqn_init"‿">u32" ⋄ F 1e20 +!"FFI: u64 argument value ≥ 2⋆53" % f←@•FFI""‿"bqn_init"‿">u64" ⋄ F 2⋆53 +!"FFI: i64 argument absolute value ≥ 2⋆53" % f←@•FFI""‿"bqn_init"‿">i64" ⋄ F 2⋆53 +!"FFI: i64 argument absolute value ≥ 2⋆53" % f←@•FFI""‿"bqn_init"‿">i64" ⋄ F -2⋆53 + + + +# bad overall argument separation +!"FFI: Expected array 𝕩" % f←@•FFI""‿"bqn_init"‿"i32"‿"i32" ⋄ F @ +!"FFI: Unnecessary 𝕨 given" % f←@•FFI""‿"bqn_init"‿"i32"‿"i32" ⋄ @ F 1‿2 +!"FFI: Wrong argument count in 𝕩: expected 2, got 3" % f←@•FFI""‿"bqn_init"‿"i32"‿"i32" ⋄ F ⟨↕2, ↕2, ↕2⟩ +!"FFI: Wrong argument count in 𝕩: expected 2, got 1" % f←@•FFI""‿"bqn_init"‿"i32"‿"i32" ⋄ F ⟨↕2⟩ +!"FFI: Wrong argument count in 𝕨: expected 2, got 3" % f←@•FFI""‿"bqn_init"‿"i32"‿"𝕨i32"‿"𝕨i32" ⋄ ⟨↕2, ↕2, ↕2⟩ F ⟨4⟩ + + + +# wrong argument internal structure +!"FFI: Expected array corresponding to ""i32:i8""" % f←@•FFI""‿"bqn_init"‿">i32:i8" ⋄ F @ +!"FFI: Bad array corresponding to ""i32:i8"": expected 4 elements, got 10" % f←@•FFI""‿"bqn_init"‿">i32:i8" ⋄ F ↕10 + +!"FFI: Expected array corresponding to a pointer" % f←@•FFI""‿"bqn_init"‿">*i32:i8" ⋄ F @ + +!"FFI: Expected array corresponding to *{...}" % f←@•FFI""‿"bqn_init"‿">*{i32}" ⋄ F @ +!"FFI: Expected array corresponding to a struct" % f←@•FFI""‿"bqn_init"‿">{i32}" ⋄ F @ +!"FFI: Expected array corresponding to a struct" % f←@•FFI""‿"bqn_init"‿">*{i32}" ⋄ F ⟨@⟩ +!"FFI: Incorrect list length corresponding to a struct: expected 1, got 0" % f←@•FFI""‿"bqn_init"‿">*{i32}" ⋄ F ⟨⟨⟩⟩ +!"FFI: Incorrect list length corresponding to a struct: expected 1, got 2" % f←@•FFI""‿"bqn_init"‿">*{i32}" ⋄ F ⟨1‿2⟩ +!"FFI: Incorrect list length corresponding to an array: expected 2, got 0" % f←@•FFI""‿"bqn_init"‿">*[2]i32" ⋄ F ⟨⟨⟩⟩ +!"FFI: Incorrect list length corresponding to an array: expected 2, got 3" % f←@•FFI""‿"bqn_init"‿">*[2]i32" ⋄ F ⟨1‿2‿3⟩ + + + +# unimplemented stuff +!"FFI: ""*"" unimplemented" % f←@•FFI""‿"bqn_init"‿">*" ⋄ F 1 +!"FFI: ""*i64"" argument type not yet implemented" % f←@•FFI""‿"bqn_init"‿">&i64" ⋄ F ↕10 +!"FFI: ""*i64"" argument type not yet implemented" % f←@•FFI""‿"bqn_init"‿">*i64" ⋄ F ↕10 +!"FFI: ""*u64"" argument type not yet implemented" % f←@•FFI""‿"bqn_init"‿">&u64" ⋄ F ↕10 +!"FFI: Pointer element type not implemented" % f←@•FFI""‿"bqn_init"‿">**u64" ⋄ F ⟨↕2⟩ + + diff --git a/test/cases/perf.bqn b/test/cases/perf.bqn index 52d692e5..26c0a425 100644 --- a/test/cases/perf.bqn +++ b/test/cases/perf.bqn @@ -1,5 +1,5 @@ -%DEF var V←•internal.Variation ⋄ LV←•internal.ListVariations -%DEF tvar2 %USE var ⋄ _tvar2_ ← {F _𝕣_ 𝕘 x: {F 𝕩 V x}¨ 𝕘 LV 𝕩; w F _𝕣_ 𝕘 x: (𝕘 LV 𝕨) {(𝕨 V w) F 𝕩 V x}⌜ 𝕘 LV 𝕩} +%DEF var V←•internal.Variation ⋄ LV←•internal.ListVariations ⋄ CLR←•internal.ClearRefs +%DEF tvar2 %USE var ⋄ _tvar2_ ← {F _𝕣_ 𝕘 x: (CLR@) ⊢ {F 𝕩 V x}¨ 𝕘 LV 𝕩; w F _𝕣_ 𝕘 x: (CLR@) ⊢ (𝕘 LV 𝕨) {(𝕨 V w) F 𝕩 V x}⌜ 𝕘 LV 𝕩} %DEF fastone ( _fastone ← { F _𝕣 𝕩: t ← 0 diff --git a/test/cases/prims.bqn b/test/cases/prims.bqn index 3a48c9d6..d8d1c99d 100644 --- a/test/cases/prims.bqn +++ b/test/cases/prims.bqn @@ -1,5 +1,5 @@ -%DEF var V←•internal.Variation ⋄ LV←•internal.ListVariations -%DEF tvar %USE var ⋄ _tvar ← {F _𝕣 x: {F 𝕩 V x}¨ LV 𝕩; w F _𝕣 x: (•internal.ClearRefs@) ⊢ (LV 𝕨) {(𝕨 V w) F 𝕩 V x}⌜ LV 𝕩} +%DEF var V←•internal.Variation ⋄ LV←•internal.ListVariations ⋄ CLR←•internal.ClearRefs +%DEF tvar %USE var ⋄ _tvar ← {F _𝕣 x: (CLR@) ⊢ {F 𝕩 V x}¨ LV 𝕩; w F _𝕣 x: (CLR@) ⊢ (LV 𝕨) {(𝕨 V w) F 𝕩 V x}⌜ LV 𝕩} %DEF k _k ← {𝔽○•internal.Keep} # batch checks diff --git a/test/cases/system.bqn b/test/cases/system.bqn index 11377240..2ae89b4a 100644 --- a/test/cases/system.bqn +++ b/test/cases/system.bqn @@ -1,7 +1,7 @@ # not tested here: •Out •Show •Exit •term •GetLine •Cmp •CurrentError •FFI •internal.HeapDump •file.MapBytes -%DEF var V←•internal.Variation ⋄ LV←•internal.ListVariations -%DEF tvar %USE var ⋄ _tvar ← {F _𝕣 x: {F 𝕩 V x}¨ LV 𝕩; w F _𝕣 x: (•internal.ClearRefs@) ⊢ (LV 𝕨) {(𝕨 V w) F 𝕩 V x}⌜ LV 𝕩} +%DEF var V←•internal.Variation ⋄ LV←•internal.ListVariations ⋄ CLR←•internal.ClearRefs +%DEF tvar %USE var ⋄ _tvar ← {F _𝕣 x: (CLR@) ⊢ {F 𝕩 V x}¨ LV 𝕩; w F _𝕣 x: (CLR@) ⊢ (LV 𝕨) {(𝕨 V w) F 𝕩 V x}⌜ LV 𝕩} # standard system values diff --git a/test/ffi/ffiTest.c b/test/ffi/ffiTest.c index f8c4cd24..9a2f4df7 100644 --- a/test/ffi/ffiTest.c +++ b/test/ffi/ffiTest.c @@ -180,6 +180,7 @@ void incInts(int32_t* a, int16_t* b, int8_t* c) { } uint64_t ident_u64(uint64_t x) { return x; } +int64_t ident_i64(int64_t x) { return x; } double ident_f64(double a) { return a; } void* pick_ptr(void** arr, int idx) { diff --git a/test/ffi/test.bqn b/test/ffi/test.bqn index de89e788..3debd61a 100644 --- a/test/ffi/test.bqn +++ b/test/ffi/test.bqn @@ -55,12 +55,17 @@ f ↩ "lib.so" •FFI ⟨"","incInts","&i32", "&i16","&i8"⟩ ⋄ •Show F ⥊ f ↩ "lib.so" •FFI ⟨"","incInts","&i32", "𝕨&i16","&i8"⟩ ⋄ •Show ⟨⥊20⟩ F ⥊¨ 10‿30 f ↩ "lib.so" •FFI ⟨"","incInts","&i32",">𝕨&i16","&i8"⟩ ⋄ •Show ⟨20⟩ F ⥊¨ 10‿30 -Section "# u64 tests" +Section "# u64 & i64 tests" f ↩ "lib.so" •FFI ⟨"u64", "ident_u64",">u64:i32"⟩ ⋄ •Show F 1234‿12 f ↩ "lib.so" •FFI ⟨"u64", "ident_u64",">u64" ⟩ ⋄ •Show F +´2⋆52‿20 +f ↩ "lib.so" •FFI ⟨"u64", "ident_u64",">u64" ⟩ ⋄ •Show F ¯1+2⋆53 f ↩ "lib.so" •FFI ⟨"u64:i32","ident_u64",">u64" ⟩ ⋄ •Show F 123456789123456 f ↩ "lib.so" •FFI ⟨"u64:u1", "ident_u64",">u64:c8" ⟩ ⋄ •Show F "hellowor" +f ↩ "lib.so" •FFI ⟨"i64:u1", "ident_i64",">i64:c8" ⟩ ⋄ •Show F "hellowor" +f ↩ "lib.so" •FFI ⟨"i64", "ident_i64",">i64"⟩ ⋄ •Show F ¯1+2⋆53 +f ↩ "lib.so" •FFI ⟨"i64", "ident_i64",">i64"⟩ ⋄ •Show F - ¯1+2⋆53 + Section "# malloc test" f ↩ "lib.so" •FFI "*:i32"‿"malloc"‿">u64" ⋄ •Show (•internal.Type⋈≠) malloc ← F 123 f ↩ "lib.so" •FFI ""‿"free"‿">*:i32" ⋄ F malloc @@ -73,7 +78,7 @@ f ↩ "lib.so" •FFI ⟨"u64:i8","pick_u64",">*u64:i8",">𝕨i32"⟩ ⋄ •Sho f ↩ "lib.so" •FFI ⟨"u64", "pick_u64",">*u64:i8",">𝕨i32"⟩ ⋄ •Show 1 F "000000001234560011122100"-'0' Section "# structs" -s1 ← "{u8,i32,i16,u64:u16,f64}" ⋄ s2 ← ∾"{"‿s1‿","‿s1‿"}" +s1 ← "{u8,i32,i16,u64:i16,f64}" ⋄ s2 ← ∾"{"‿s1‿","‿s1‿"}" f ↩ "lib.so" •FFI ⟨"i16", "thirdMember", ">"∾s1⟩ ⋄ •Show F ⟨200, 2e9, ¯30000, 1‿2‿3‿4, 3.25⟩ f ↩ "lib.so" •FFI ⟨s1, "incMembers", ">"∾s1⟩ ⋄ •Show F ⟨200, 2e9, ¯30000, 1‿2‿3‿4, 3.25⟩ f ↩ "lib.so" •FFI ⟨"&", "incMany", "&"∾s2, "u64"⟩ ⋄ •Show¨ F ⟨0‿3‿6+3⥊<0‿1+2⥊<⟨200, 2e9, ¯30000, 1‿2‿3‿4, 3.25⟩, 3⟩ diff --git a/test/ffi/test.expected b/test/ffi/test.expected index a72ef9ad..b336bcc5 100644 --- a/test/ffi/test.expected +++ b/test/ffi/test.expected @@ -72,11 +72,15 @@ ff7fdfefefdf7bb4 ff7fdfefefdf7bb4 fefffdfff7ffbffb bffff7fffdfffeff 10 20 30 ⟨ ⟨ 11 ⟩ ⟨ 21 ⟩ ⟨ 31 ⟩ ⟩ -# u64 tests +# u64 & i64 tests 51539608786 4.503599628419072e15 +9.007199254740991e15 ⟨ ¯2045800064 28744 ⟩ ⟨ 0 0 0 1 0 1 1 0 1 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 1 1 1 1 0 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 0 0 1 0 0 1 1 1 0 ⟩ +⟨ 0 0 0 1 0 1 1 0 1 0 1 0 0 1 1 0 0 0 1 1 0 1 1 0 0 0 1 1 0 1 1 0 1 1 1 1 0 1 1 0 1 1 1 0 1 1 1 0 1 1 1 1 0 1 1 0 0 1 0 0 1 1 1 0 ⟩ +9.007199254740991e15 +¯9.007199254740991e15 # malloc test ⟨ "i32arr" 2 ⟩ diff --git a/test/run.bqn b/test/run.bqn index 930be935..8fe07773 100644 --- a/test/run.bqn +++ b/test/run.bqn @@ -21,7 +21,7 @@ Trim ← {((∨`⌾⌽∧∨`)𝕩≠' ')/𝕩} •Out " fuzz perf" •Out " For specific configurations:" •Out " test_range // -DTEST_RANGE" - •Out "Lint all: test/run.bqn lint prims cells syntax system fills hash patterns under fuzz perf test_range" + •Out "Lint all: test/run.bqn lint prims cells syntax system fills hash patterns under fuzz perf ffi test_range" •Exit 0 }⍟⊢ (0=≠•args) ∨´ "help"‿"h"‿"?"∊'-'⊸≠⊸/¨•args