From 6fbe5ca8a50a282a780c60a78a473efce45a0d54 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Wed, 9 Sep 2026 15:36:57 -0700 Subject: [PATCH] the interpreter's [extern] call marshals through an arm64 layout past six arguments - the generated C wrappers spell every stack argument as an 8-byte slot and mask six float positions, while Apple packs stack arguments at natural size and every arm64 has eight float registers, so LLVMDIBuilderCreateFunction's tail arguments read one slot late on every Mac (the debug-info test red since #3981) and a float in position 6 or 7 rode an integer register on Linux and Android arm64; the layout maps each argument to x0-x7, d0-d7 or a byte offset of a stack image (packed on Apple, slotted on the GNU ABI), an assembly trampoline copies the image and calls, and layouts live for the process beside the bound-function table since a re-applied extern replaces the function object an earlier context's node still names; two table defects fall with it - the wrapper choice read the float mask of the first four SystemV positions instead of six, so a double in position 4 or 5 went through an integer register on every SystemV host, and the extra table's result class was inverted against the main table's, so its hand-listed GL wrappers returned rax where the callee's xmm0 held the value - and a float in position 6 now routes to the extra table instead of being dropped by the six-bit mask; tests/dasbind probes every shape against a library of functions that sum their arguments under distinct multipliers, built into lib/ by its own CMake, gated by the .das_test walk on the library's presence and a 64-bit host, registered in the AOT suites as a no_aot file, with its two SystemV shapes listed in the generator; src/builtin/ARCHITECTURE.md sec.3 carries the mechanism and REVIEW.md the three-box duty Co-Authored-By: Claude Fable 5.1 --- .github/workflows/extended_checks.yml | 6 +- CMakeLists.txt | 3 + src/builtin/ARCHITECTURE.md | 50 +++++++ src/builtin/REVIEW.md | 19 ++- src/builtin/generate_x86_64_calls.das | 37 +++-- src/builtin/module_builtin_dasbind.cpp | 169 ++++++++++++++++++++--- src/builtin/systemV_64_extra_wrapper.inc | 80 +++++++---- tests/.das_test | 16 +++ tests/REVIEW.md | 8 ++ tests/aot/CMakeLists.txt | 2 +- tests/dasbind/CMakeLists.txt | 24 ++++ tests/dasbind/_reapply_fixture.das | 13 ++ tests/dasbind/probe/dasbind_probe.cpp | 54 ++++++++ tests/dasbind/test_extern_abi.das | 97 +++++++++++++ 14 files changed, 510 insertions(+), 68 deletions(-) create mode 100644 tests/REVIEW.md create mode 100644 tests/dasbind/CMakeLists.txt create mode 100644 tests/dasbind/_reapply_fixture.das create mode 100644 tests/dasbind/probe/dasbind_probe.cpp create mode 100644 tests/dasbind/test_extern_abi.das diff --git a/.github/workflows/extended_checks.yml b/.github/workflows/extended_checks.yml index 7a3162d00f..b675a06e1c 100644 --- a/.github/workflows/extended_checks.yml +++ b/.github/workflows/extended_checks.yml @@ -228,7 +228,7 @@ jobs: # No sccache launcher: this lane is nightly-only and runs uncached. cmake --no-warn-unused-cli -B./build -G "${{ matrix.cmake_generator }}" -DCMAKE_BUILD_TYPE:STRING=Release \ $ACTIVE_MODULES -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" - cmake --build ./build --config Release --target daslang daslang_static --parallel + cmake --build ./build --config Release --target daslang daslang_static dasbind_probe --parallel ;; linux) echo "BIN=./bin" >> $GITHUB_ENV @@ -245,7 +245,7 @@ jobs: -DDAS_CLANG_BIND_DISABLED=ON \ -DDAS_GLFW_DISABLED=OFF \ -G "${{ matrix.cmake_generator }}" $ACTIVE_MODULES - cmake --build ./build --config Release --target daslang daslang_static + cmake --build ./build --config Release --target daslang daslang_static dasbind_probe ;; *) echo "BIN=./bin" >> $GITHUB_ENV @@ -254,7 +254,7 @@ jobs: -DCMAKE_OSX_ARCHITECTURES="${{ matrix.architecture_string }}" \ -DDAS_GLFW_DISABLED=OFF \ -G "${{ matrix.cmake_generator }}" $ACTIVE_MODULES - cmake --build ./build --config Release --target daslang daslang_static + cmake --build ./build --config Release --target daslang daslang_static dasbind_probe ;; esac diff --git a/CMakeLists.txt b/CMakeLists.txt index 44a817832d..4aed71ff86 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1634,6 +1634,9 @@ if (NOT ${DAS_TOOLS_DISABLED}) # test time + dasModuleSQLITE; both must already be defined as targets). add_subdirectory(tests/exe-paths) + # The probe library tests/dasbind binds through [extern] (lands in lib/). + add_subdirectory(tests/dasbind) + # Build daslang utilities as standalone executables (-exe mode) add_subdirectory(utils) diff --git a/src/builtin/ARCHITECTURE.md b/src/builtin/ARCHITECTURE.md index 0e0ceb1250..467e527fd8 100644 --- a/src/builtin/ARCHITECTURE.md +++ b/src/builtin/ARCHITECTURE.md @@ -67,3 +67,53 @@ set; `0` disables eviction), never the record just written. `install` touches th reads, so a record in use is the newest and a stale variant the oldest. Only the default directory is pruned - an explicit `-module-cache ` is the user's - and the limit variable is the one `DAS*` name the record key skips, since it decides nothing about a compile. + +## 3. The interpreter's `[extern]` call + +An `[extern]` function (`module_builtin_dasbind.cpp`) is called from the interpreter through +a wrapper `vec4f (*)(void * fn, vec4f * args)`: the node evaluates every argument into a +`vec4f` lane and the wrapper calls `fn` through a C prototype. The wrappers are generated - +`generate_x86_64_calls.das` writes `win_x86_64_wrapper.inc`, `systemV_64_wrapper.inc` and +`systemV_64_extra_wrapper.inc` - one per argument count, result class and float mask of the +first register-class arguments, indexed from 0: four positions on Windows, six on SystemV, +with every integer-class argument spelled `int64_t`, every float-class one `double`, and the +result `int64_t` or `vec4f`. Float class is a `float` or `double` by value; a ref, whatever it +points to, is a pointer and so integer class, on the table and the layout alike. A SystemV call with more than six arguments and a float at index +6 or later needs a prototype from the extra table, keyed by argument count, result class (1 for +a float or double result, as the main table indexes it) and the full mask; its entries are the +hand-listed `systemV_extra` rows in the generator - a new shape is added there and the +generator re-run - and a shape the list lacks is a compile error naming the mask. + +The C prototype passes a stack argument as an 8-byte slot. That fits Windows x64, where the +mask covers four positions and everything past the fourth argument is an 8-byte stack slot, +float included. It fits SystemV x86-64 too: its float registers past index 5 are exactly the +extra list's cases. It does not fit arm64: eight float registers, so a float at index 6 or 7 +belongs in d6 or d7, and on Apple the stack is packed at natural size and alignment - a 32-bit +`int` in 4 bytes, a `bool` in 1 - where Linux and Android keep 8-byte slots. So on arm64 +(Apple, Linux, Android; a Windows arm64 build keeps the table, since `DAS_BIND_ARM64_LAYOUT` +turns on for `__APPLE__` and `__linux__` only while `DAS_BIND_EXTERNAL` is on for every 64-bit +Windows) a call with more than six arguments leaves the table: at bind time the +function's das types produce an `Arm64Layout` - which argument rides in x0-x7, which in d0-d7, +and at which byte offset of a stack image the rest sit, packed on Apple and slotted elsewhere - +and the node calls `das_arm64_call`, an assembly trampoline that copies the image below its +own frame, loads the sixteen argument registers, calls, and stores x0 and d0 for the node to +return. A call of six or fewer arguments has no stack argument and no float past the mask +under any of these ABIs, so it keeps the table there too. A packed width comes from the das +type: a ref, pointer or string is 8 bytes, a scalar its `getBaseSizeOf`, and anything else (an +aggregate by value, which the binder does not support) rides as 8 bytes, as the table would +pass it. A layout lives for the process - `computeArm64Layout` keeps every layout in a static +vector it never frees - because a re-applied `[extern]` replaces the module's function object +while an earlier context's call node, which carries the layout pointer, may still run. + +The JIT uses neither: `modules/dasLLVM/daslib/llvm_jit.das` (repo root) emits an extern call +with the function's own LLVM types, so a compiled call is right on every platform. The wrapper +table and the layout are the interpreter's alone, which is why an ABI defect there shows only +in interpreted code - a macro context, a `[no_jit]` function, a plain run. `tests/dasbind` +probes both paths against `tests/dasbind/probe`, a library whose functions sum their arguments +under distinct multipliers, so one argument in the wrong register or slot changes the total. +`tests/dasbind/CMakeLists.txt` builds the probe on a 64-bit host that builds and dlopens a +shared library at test time - not wasm, Android or iOS, none of which walks `tests/` - and +`tests/.das_test` skips the suite only on a 32-bit host, where the probe is not built, and under +dastest's `--ser`/`--deser` sweep, because a deserialized program never applies `[extern]` and +so never manufactures the `__dasbind__` function it names in the `dasbind` module; a 64-bit +desktop tree without the library fails the suite instead of skipping it. diff --git a/src/builtin/REVIEW.md b/src/builtin/REVIEW.md index faae95e0f1..79aaccbd70 100644 --- a/src/builtin/REVIEW.md +++ b/src/builtin/REVIEW.md @@ -8,9 +8,9 @@ `strings` and `jit`. What the scan enforces, and which shared generic helpers it exempts, is read from the scan itself. -- **A diff that adds or changes a bind in a module the scan covers rebuilds the binary from - that diff before the folder's gate runs** - the scan reads the binds compiled into the running - binary, so a stale binary is a false green. +- **A diff that adds or changes a bind - any `addExtern*` call - in a module the scan covers + rebuilds the binary from that diff before the folder's gate runs** - the scan reads the binds + compiled into the running binary, so a stale binary is a false green. - **A diff that adds a module under this folder adds it to `review_nttp.das`'s `require` list in the same change - directly, or through the daslib wrapper that requires it.** A module the list @@ -47,13 +47,18 @@ in the same change.** A replayed start never runs the descriptor, so an effect the recorder does not see is an effect every warm start silently lacks. -- **A diff that moves a bind between modules - an `addExtern` or `addExternInline` call whose - module changes, or a builtin whose `vector` functions follow a type to another module - - bumps `LLVM_JIT_CODEGEN_VERSION` in `modules/dasLLVM/daslib/llvm_jit_plan.das` (repo root), in - the same change.** The JIT's DLL cache key folds the codegen version and each function's AST +- **A diff that moves a bind between modules - any `addExtern*` call whose module changes, or a + builtin whose `vector` functions follow a type to another module - bumps + `LLVM_JIT_CODEGEN_VERSION` in `modules/dasLLVM/daslib/llvm_jit_plan.das` (repo root), in the + same change.** The JIT's DLL cache key folds the codegen version and each function's AST hash, never the module an extern lives in, so a cached DLL binds the old name and crashes on the hit. +- **A diff that changes the wrapper tables, the `systemV_extra` list, the arm64 layout or the + `das_arm64_call` trampoline updates section 3 of `ARCHITECTURE.md` in the same change.** Two + comments in `module_builtin_dasbind.cpp` cite that section instead of restating it, so a + stale section is what the next reader trusts. + - **A diff that changes what `ModuleFileCache::defaultPath` folds into the module-cache key - the binary, the command line, the environment names, or which script arguments count - updates the cache-key paragraph of `ARCHITECTURE.md` in the same change.** The key is what stops a diff --git a/src/builtin/generate_x86_64_calls.das b/src/builtin/generate_x86_64_calls.das index 9419b5f97b..bda23762db 100644 --- a/src/builtin/generate_x86_64_calls.das +++ b/src/builtin/generate_x86_64_calls.das @@ -85,9 +85,17 @@ let ARG12_D = (1 << 12) let ARG13_D = (1 << 13) let ARG14_D = (1 << 14) let ARG15_D = (1 << 15) +let ARG16_D = (1 << 16) +let ARG17_D = (1 << 17) +let ARG18_D = (1 << 18) +let ARG19_D = (1 << 19) +let ARG20_D = (1 << 20) +let ARG21_D = (1 << 21) +let ARG22_D = (1 << 22) +let ARG23_D = (1 << 23) -let RES_D = 0 -let RES_X = 1 +let RES_FLOAT = 1 +let RES_INT = 0 struct WrapperFn { nargs : int @@ -110,18 +118,27 @@ def genExtraWrapper(name : string; extra : array) { let systemV_extra <- [WrapperFn( // glPrimitiveBoundingBoxARB - nargs=8, res=1, perm=0 | ARG0_D | ARG1_D | ARG2_D | ARG3_D | ARG4_D | ARG5_D | ARG6_D | ARG7_D), WrapperFn( + nargs=8, res=RES_INT, perm=0 | ARG0_D | ARG1_D | ARG2_D | ARG3_D | ARG4_D | ARG5_D | ARG6_D | ARG7_D), WrapperFn( // glDrawVkImageNV - nargs=11, res=1, perm=0 | ARG2_D | ARG3_D | ARG4_D | ARG5_D | ARG6_D | ARG7_D | ARG8_D | ARG9_D | ARG10_D), WrapperFn( + nargs=11, res=RES_INT, perm=0 | ARG2_D | ARG3_D | ARG4_D | ARG5_D | ARG6_D | ARG7_D | ARG8_D | ARG9_D | ARG10_D), WrapperFn( // glPathGlyphsNV - nargs=10, res=1, perm=0 | ARG9_D), WrapperFn( + nargs=10, res=RES_INT, perm=0 | ARG9_D), WrapperFn( // glPathGlyphRangeNV, glPathMemoryGlyphIndexArrayNV - nargs=9, res=1, perm=0 | ARG8_D), WrapperFn( + nargs=9, res=RES_INT, perm=0 | ARG8_D), WrapperFn( // glPathGlyphIndexArrayNV - nargs=8, res=1, perm=0 | ARG7_D), WrapperFn( -// DUMMY - nargs = 13, res = RES_X, perm=ARG0_D | ARG13_D), WrapperFn( - nargs = 13, res = RES_D, perm=ARG0_D | ARG13_D + nargs=8, res=RES_INT, perm=0 | ARG7_D), WrapperFn( +// glMatrixFrustumEXT, glMatrixOrthoEXT + nargs=7, res=RES_INT, perm=0 | ARG1_D | ARG2_D | ARG3_D | ARG4_D | ARG5_D | ARG6_D), WrapperFn( +// glNamedProgramLocalParameter4dEXT, glNamedProgramLocalParameter4fEXT + nargs=7, res=RES_INT, perm=0 | ARG3_D | ARG4_D | ARG5_D | ARG6_D), WrapperFn( +// glGetPathSpacingNV + nargs=9, res=RES_INT, perm=0 | ARG5_D | ARG6_D), WrapperFn( +// tests/dasbind probe_dbl_at6 + nargs=8, res=RES_FLOAT, perm=0 | ARG6_D), WrapperFn( +// tests/dasbind probe_dbl_at67 + nargs=8, res=RES_FLOAT, perm=0 | ARG6_D | ARG7_D), WrapperFn( +// tests/dasbind probe_stack_narrow + nargs=20, res=RES_FLOAT, perm=0 | ARG0_D | ARG1_D | ARG2_D | ARG3_D | ARG4_D | ARG5_D | ARG6_D | ARG7_D | ARG18_D )]; [export] diff --git a/src/builtin/module_builtin_dasbind.cpp b/src/builtin/module_builtin_dasbind.cpp index 574eb8c9e8..d49e32835e 100644 --- a/src/builtin/module_builtin_dasbind.cpp +++ b/src/builtin/module_builtin_dasbind.cpp @@ -12,8 +12,67 @@ #include "daScript/misc/sysos.h" #include "daScript/misc/env_cfg.h" +#include +#include #include +#if DAS_BIND_EXTERNAL && defined(__aarch64__) && (defined(__APPLE__) || defined(__linux__)) +#define DAS_BIND_ARM64_LAYOUT 1 +#else +#define DAS_BIND_ARM64_LAYOUT 0 +#endif +#if DAS_BIND_ARM64_LAYOUT && defined(__APPLE__) +#define DAS_BIND_ARM64_PACKED_STACK 1 +#else +#define DAS_BIND_ARM64_PACKED_STACK 0 +#endif + +#if DAS_BIND_ARM64_LAYOUT +#if defined(__APPLE__) +#define DAS_ARM64_CALL_SYMBOL "_das_arm64_call" +#else +#define DAS_ARM64_CALL_SYMBOL "das_arm64_call" +#endif +// src/builtin/ARCHITECTURE.md sec.3 +__asm__( + ".text\n" + ".p2align 2\n" + ".globl " DAS_ARM64_CALL_SYMBOL "\n" + DAS_ARM64_CALL_SYMBOL ":\n" + " stp x29, x30, [sp, #-32]!\n" + " stp x19, x20, [sp, #16]\n" + " mov x29, sp\n" + " mov x19, x0\n" + " mov x20, x5\n" + " sub sp, sp, x4\n" + " mov x9, #0\n" + "1: cmp x9, x4\n" + " b.hs 2f\n" + " ldr x10, [x3, x9]\n" + " str x10, [sp, x9]\n" + " add x9, x9, #8\n" + " b 1b\n" + "2: ldp d0, d1, [x2]\n" + " ldp d2, d3, [x2, #16]\n" + " ldp d4, d5, [x2, #32]\n" + " ldp d6, d7, [x2, #48]\n" + " ldp x6, x7, [x1, #48]\n" + " ldp x4, x5, [x1, #32]\n" + " ldp x2, x3, [x1, #16]\n" + " ldp x0, x1, [x1]\n" + " blr x19\n" + " str x0, [x20]\n" + " str d0, [x20, #8]\n" + " mov sp, x29\n" + " ldp x19, x20, [sp, #16]\n" + " ldp x29, x30, [sp], #32\n" + " ret\n" +); +struct Arm64Result { uint64_t x0, d0; }; +extern "C" void das_arm64_call ( void * fn, const uint64_t * gpr, const uint64_t * fpr, + const uint8_t * stack, uint64_t stackBytes, Arm64Result * out ); +#endif + namespace das { #if DAS_BIND_EXTERNAL @@ -85,6 +144,12 @@ namespace das { FastCallWrapper wrapper; }; + enum { RES_INT = 0, RES_FLOAT = 1 }; + + static bool isFloatClass ( const TypeDeclPtr & type ) { + return !type->isRef() && (type->baseType==Type::tFloat || type->baseType==Type::tDouble); + } + __forceinline vec4f Rx ( int64_t x ) { return v_cast_vec4f(v_splatsi64(x)); } #define AX(i) (*(uint64_t *)(args+(i))) @@ -110,6 +175,68 @@ FastCallWrapper getExtraWrapper ( int nargs, int res, int perm ) { #undef AX #undef AD + // src/builtin/ARCHITECTURE.md sec.3 + struct Arm64Layout { + struct Slot { uint8_t argIndex, bytes; uint16_t offset; }; + uint8_t gprArg[8] = {}, fprArg[8] = {}; + int ngpr = 0, nfpr = 0; + vector stack; + uint32_t stackBytes = 0; + bool fpResult = false; + }; + +#if DAS_BIND_ARM64_LAYOUT + static uint8_t arm64StackArgumentBytes ( const TypeDeclPtr & type ) { + if ( !DAS_BIND_ARM64_PACKED_STACK || type->isRef() ) return 8; + auto size = type->getBaseSizeOf(); + return (size==1 || size==2 || size==4) ? uint8_t(size) : 8; + } + + static const Arm64Layout * computeArm64Layout ( Function * fun ) { + if ( fun->arguments.size()<=6 ) return nullptr; + auto layout = make_unique(); + uint32_t offset = 0; + for ( int a=0, as=int(fun->arguments.size()); aarguments[a]->type; + bool fp = isFloatClass(type); + int & count = fp ? layout->nfpr : layout->ngpr; + if ( count<8 ) { + (fp ? layout->fprArg : layout->gprArg)[count++] = uint8_t(a); + } else { + uint8_t bytes = arm64StackArgumentBytes(type); + offset = (offset + bytes - 1) & ~uint32_t(bytes - 1); + layout->stack.push_back({uint8_t(a), bytes, uint16_t(offset)}); + offset += bytes; + } + } + layout->stackBytes = (offset + 15) & ~15u; + layout->fpResult = isFloatClass(fun->result); + static mutex layoutsMutex; + static vector> layouts; + lock_guard guard(layoutsMutex); + layouts.push_back(das::move(layout)); + return layouts.back().get(); + } + + static vec4f arm64Call ( void * fn, vec4f * args, const Arm64Layout * layout ) { + uint64_t gpr[8] = {}, fpr[8] = {}; + Arm64Result out = {}; + for ( int i=0; i!=layout->ngpr; ++i ) memcpy(gpr+i, args+layout->gprArg[i], 8); + for ( int i=0; i!=layout->nfpr; ++i ) memcpy(fpr+i, args+layout->fprArg[i], 8); + alignas(16) uint8_t stack[DAS_MAX_FUNCTION_ARGUMENTS*8 + 16] = {}; + for ( const auto & slot : layout->stack ) memcpy(stack+slot.offset, args+slot.argIndex, slot.bytes); + das_arm64_call(fn, gpr, fpr, stack, layout->stackBytes, &out); + uint64_t raw = layout->fpResult ? out.d0 : out.x0; + int64_t bits; + memcpy(&bits, &raw, sizeof(bits)); + return v_cast_vec4f(v_splatsi64(bits)); + } +#else + static const Arm64Layout * computeArm64Layout ( Function * ) { + return nullptr; + } +#endif + struct BoundFunction { void * fun; string name; @@ -246,8 +373,9 @@ FastCallWrapper getExtraWrapper ( int nargs, int res, int perm ) { } struct SimNode_DasBindCall : SimNode_ExtFuncCallBase { - SimNode_DasBindCall ( const LineInfo & a, const char * fnName, uint64_t hc, FastCallWrapper wrp, void * fun, ApiType api_ ) - : SimNode_ExtFuncCallBase(a, fnName), code(hc), wrapper(wrp), fnptr(fun), api(api_) { + SimNode_DasBindCall ( const LineInfo & a, const char * fnName, uint64_t hc, FastCallWrapper wrp, void * fun, ApiType api_, + const Arm64Layout * layout_ ) + : SimNode_ExtFuncCallBase(a, fnName), code(hc), wrapper(wrp), fnptr(fun), api(api_), layout(layout_) { } void bind ( Context & context ) { string crash_and_burn; @@ -270,20 +398,23 @@ FastCallWrapper getExtraWrapper ( int nargs, int res, int perm ) { } vec4f argValues[DAS_MAX_FUNCTION_ARGUMENTS]; evalArgs(context, argValues); +#if DAS_BIND_ARM64_LAYOUT + if ( layout ) return arm64Call(fnptr, argValues, layout); +#endif return wrapper(fnptr, argValues); } uint64_t code = 0; FastCallWrapper wrapper = nullptr; void * fnptr = nullptr; ApiType api = ApiType::api_unknown; + const Arm64Layout * layout = nullptr; }; FastCallWrapper getWrapper ( Function * fun, int nReg ) { - int args = ( fun->result->baseType==Type::tFloat || fun->result->baseType==Type::tDouble ) ? (1<result) ? (1<arguments.size()); aarguments[a]->type->baseType; - if ( tp==Type::tFloat || tp==Type::tDouble ) { + if ( a==nReg ) break; + if ( isFloatClass(fun->arguments[a]->type) ) { args |= (1< & ) override { const char * fnName = context.code->allocateName(this->name); - return context.code->makeNode(at, fnName, hc, wrapper, dllAddress, api); + return context.code->makeNode(at, fnName, hc, wrapper, dllAddress, api, layout); } }; @@ -391,15 +524,14 @@ FastCallWrapper getExtraWrapper ( int nargs, int res, int perm ) { #else if ( fun->arguments.size()>6 ) { int nargs = int(fun->arguments.size()); - int res = ( fun->result->baseType==Type::tFloat || fun->result->baseType==Type::tDouble ) ? 0 : 1; + int res = isFloatClass(fun->result) ? RES_FLOAT : RES_INT; int perm = 0; for ( size_t ai=0, ais=fun->arguments.size(); ai!=ais; ++ai ) { - const auto & a = fun->arguments[ai]; - if ( a->type->isSimpleType(Type::tFloat) || a->type->isSimpleType(Type::tDouble) ) { + if ( isFloatClass(fun->arguments[ai]->type) ) { perm |= (1<=(1<<7) ) { + if ( perm>=(1<<6) ) { return getExtraWrapper(nargs, res, perm); } } @@ -446,17 +578,16 @@ FastCallWrapper getExtraWrapper ( int nargs, int res, int perm ) { return false; } #ifndef _MSC_VER - if ( fun->arguments.size()>6 ) { + if ( fun->arguments.size()>6 && !DAS_BIND_ARM64_LAYOUT ) { int perm=0; int nargs = int(fun->arguments.size()); - int res = ( fun->result->baseType==Type::tFloat || fun->result->baseType==Type::tDouble ) ? 0 : 1; + int res = isFloatClass(fun->result) ? RES_FLOAT : RES_INT; for ( size_t ai=0, ais=fun->arguments.size(); ai!=ais; ++ai ) { - const auto & arg = fun->arguments[ai]; - if ( arg->type->isSimpleType(Type::tFloat) || arg->type->isSimpleType(Type::tDouble) ) { + if ( isFloatClass(fun->arguments[ai]->type) ) { perm |= (1<=(1<<7) ) { + if ( perm>=(1<<6) ) { auto wrp = getExtraWrapper(nargs,res,perm); if ( !wrp ) { string argText; @@ -496,7 +627,7 @@ FastCallWrapper getExtraWrapper ( int nargs, int res, int perm ) { } auto wrp = computeWrapper(fun); uint64_t code = lateBind(ba.fn_name, ba.library, funptr); - auto bif = new DasBindFunction(bindName, code, funptr, ba, wrp); + auto bif = new DasBindFunction(bindName, code, funptr, ba, wrp, computeArm64Layout(fun)); bif->result = fun->result; for ( auto & a : fun->arguments ) { auto newArg = new Variable(); diff --git a/src/builtin/systemV_64_extra_wrapper.inc b/src/builtin/systemV_64_extra_wrapper.inc index 42ba514e2c..4418d216a9 100644 --- a/src/builtin/systemV_64_extra_wrapper.inc +++ b/src/builtin/systemV_64_extra_wrapper.inc @@ -1,45 +1,69 @@ -vec4f fastcall64_8_1_255 ( void * fn, vec4f * args ) { - using call_kind = vec4f ( * ) ( double,double,double,double,double,double,double,double ); - return call_kind(fn) ( AD(0),AD(1),AD(2),AD(3),AD(4),AD(5),AD(6),AD(7) ); +vec4f fastcall64_8_0_255 ( void * fn, vec4f * args ) { + using call_kind = int64_t ( * ) ( double,double,double,double,double,double,double,double ); + return Rx ( call_kind(fn) ( AD(0),AD(1),AD(2),AD(3),AD(4),AD(5),AD(6),AD(7) ) ); } -vec4f fastcall64_11_1_2044 ( void * fn, vec4f * args ) { - using call_kind = vec4f ( * ) ( int64_t,int64_t,double,double,double,double,double,double,double,double,double ); - return call_kind(fn) ( AX(0),AX(1),AD(2),AD(3),AD(4),AD(5),AD(6),AD(7),AD(8),AD(9),AD(10) ); +vec4f fastcall64_11_0_2044 ( void * fn, vec4f * args ) { + using call_kind = int64_t ( * ) ( int64_t,int64_t,double,double,double,double,double,double,double,double,double ); + return Rx ( call_kind(fn) ( AX(0),AX(1),AD(2),AD(3),AD(4),AD(5),AD(6),AD(7),AD(8),AD(9),AD(10) ) ); } -vec4f fastcall64_10_1_512 ( void * fn, vec4f * args ) { - using call_kind = vec4f ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double ); - return call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AX(5),AX(6),AX(7),AX(8),AD(9) ); +vec4f fastcall64_10_0_512 ( void * fn, vec4f * args ) { + using call_kind = int64_t ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double ); + return Rx ( call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AX(5),AX(6),AX(7),AX(8),AD(9) ) ); } -vec4f fastcall64_9_1_256 ( void * fn, vec4f * args ) { - using call_kind = vec4f ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double ); - return call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AX(5),AX(6),AX(7),AD(8) ); +vec4f fastcall64_9_0_256 ( void * fn, vec4f * args ) { + using call_kind = int64_t ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double ); + return Rx ( call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AX(5),AX(6),AX(7),AD(8) ) ); } -vec4f fastcall64_8_1_128 ( void * fn, vec4f * args ) { - using call_kind = vec4f ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double ); - return call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AX(5),AX(6),AD(7) ); +vec4f fastcall64_8_0_128 ( void * fn, vec4f * args ) { + using call_kind = int64_t ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double ); + return Rx ( call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AX(5),AX(6),AD(7) ) ); } -vec4f fastcall64_13_1_8193 ( void * fn, vec4f * args ) { - using call_kind = vec4f ( * ) ( double,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t ); - return call_kind(fn) ( AD(0),AX(1),AX(2),AX(3),AX(4),AX(5),AX(6),AX(7),AX(8),AX(9),AX(10),AX(11),AX(12) ); +vec4f fastcall64_7_0_126 ( void * fn, vec4f * args ) { + using call_kind = int64_t ( * ) ( int64_t,double,double,double,double,double,double ); + return Rx ( call_kind(fn) ( AX(0),AD(1),AD(2),AD(3),AD(4),AD(5),AD(6) ) ); } -vec4f fastcall64_13_0_8193 ( void * fn, vec4f * args ) { - using call_kind = int64_t ( * ) ( double,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t ); - return Rx ( call_kind(fn) ( AD(0),AX(1),AX(2),AX(3),AX(4),AX(5),AX(6),AX(7),AX(8),AX(9),AX(10),AX(11),AX(12) ) ); +vec4f fastcall64_7_0_120 ( void * fn, vec4f * args ) { + using call_kind = int64_t ( * ) ( int64_t,int64_t,int64_t,double,double,double,double ); + return Rx ( call_kind(fn) ( AX(0),AX(1),AX(2),AD(3),AD(4),AD(5),AD(6) ) ); +} + +vec4f fastcall64_9_0_96 ( void * fn, vec4f * args ) { + using call_kind = int64_t ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,double,double,int64_t,int64_t ); + return Rx ( call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AD(5),AD(6),AX(7),AX(8) ) ); +} + +vec4f fastcall64_8_1_64 ( void * fn, vec4f * args ) { + using call_kind = vec4f ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double,int64_t ); + return call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AX(5),AD(6),AX(7) ); +} + +vec4f fastcall64_8_1_192 ( void * fn, vec4f * args ) { + using call_kind = vec4f ( * ) ( int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double,double ); + return call_kind(fn) ( AX(0),AX(1),AX(2),AX(3),AX(4),AX(5),AD(6),AD(7) ); +} + +vec4f fastcall64_20_1_262399 ( void * fn, vec4f * args ) { + using call_kind = vec4f ( * ) ( double,double,double,double,double,double,double,double,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,int64_t,double,int64_t ); + return call_kind(fn) ( AD(0),AD(1),AD(2),AD(3),AD(4),AD(5),AD(6),AD(7),AX(8),AX(9),AX(10),AX(11),AX(12),AX(13),AX(14),AX(15),AX(16),AX(17),AD(18),AX(19) ); } FastCallExtraWrapper fastcall64_extra_table [] = { - 8,1,255,&fastcall64_8_1_255, - 11,1,2044,&fastcall64_11_1_2044, - 10,1,512,&fastcall64_10_1_512, - 9,1,256,&fastcall64_9_1_256, - 8,1,128,&fastcall64_8_1_128, - 13,1,8193,&fastcall64_13_1_8193, - 13,0,8193,&fastcall64_13_0_8193, + 8,0,255,&fastcall64_8_0_255, + 11,0,2044,&fastcall64_11_0_2044, + 10,0,512,&fastcall64_10_0_512, + 9,0,256,&fastcall64_9_0_256, + 8,0,128,&fastcall64_8_0_128, + 7,0,126,&fastcall64_7_0_126, + 7,0,120,&fastcall64_7_0_120, + 9,0,96,&fastcall64_9_0_96, + 8,1,64,&fastcall64_8_1_64, + 8,1,192,&fastcall64_8_1_192, + 20,1,262399,&fastcall64_20_1_262399, }; diff --git a/tests/.das_test b/tests/.das_test index 3b51cbc972..2b5230fe88 100644 --- a/tests/.das_test +++ b/tests/.das_test @@ -5,6 +5,22 @@ require daslib/rtti [export, pinvoke] def can_visit_folder(folder_name : string; var result : bool?) { + // the probe suite runs on 64-bit hosts only - tests/dasbind/CMakeLists.txt builds its library nowhere + // else; a 64-bit tree missing that library fails the suite loudly. It skips the ser/deser sweep: a + // deserialized program never applies [extern], so the bound function it names is not in dasbind + if (folder_name == "dasbind") { + *result = typeinfo sizeof(type) == 8 + if (*result) { + let args <- get_command_line_arguments() + for (arg in args) { + if (arg == "--ser" || arg == "--deser") { + *result = false + return + } + } + } + return + } if (folder_name == "clipboard") { *result = has_module("clipboard_core") return diff --git a/tests/REVIEW.md b/tests/REVIEW.md new file mode 100644 index 0000000000..e49641cb84 --- /dev/null +++ b/tests/REVIEW.md @@ -0,0 +1,8 @@ +# Tests Code Review Checklist + +**Read `REVIEW_COMMON.md` (repo root) first - its contract binds this checklist.** + +- **A diff that widens the `dasbind` skip in `.das_test` or drops a probe shape from + `dasbind/test_extern_abi.das` is a defect.** The suite is the only check of which register or + stack slot an interpreted `[extern]` call puts each argument in - the JIT never takes that + path - so coverage lost there reports green on every lane. diff --git a/tests/aot/CMakeLists.txt b/tests/aot/CMakeLists.txt index cbd2c5f89e..7b6a127698 100644 --- a/tests/aot/CMakeLists.txt +++ b/tests/aot/CMakeLists.txt @@ -389,7 +389,7 @@ set(TEST_AOT_ALL_DAS "") # test-body .das (not module libs); LLVM-AOT corpus e set(DAS_AOT_SUITES algorithm apply archive assert_once ast_match async bare_block base64 bitfields bool_array - class_boost daslib dasgltf daspeg data_walker debug debug_agent decs dynamic_cast_rtti fio + class_boost dasbind daslib dasgltf daspeg data_walker debug debug_agent decs dynamic_cast_rtti fio fixed_array flatten fs functional gc glsl handle_types hash_map interfaces jit jobque json jsonrpc language linq lint long_array_table loops lpipe lsp macro_boost macro_call match math mcp md_boost module_cache module_tests option promote quote diff --git a/tests/dasbind/CMakeLists.txt b/tests/dasbind/CMakeLists.txt new file mode 100644 index 0000000000..5de3e0f0a7 --- /dev/null +++ b/tests/dasbind/CMakeLists.txt @@ -0,0 +1,24 @@ +# The probe library tests/dasbind binds through [extern]: C functions whose argument shapes +# reach past the callee's registers. Built only on a host that builds and dlopens a shared +# library at test time - the mobile and wasm targets that also run the extern path share an +# ABI with a desktop lane that does - and placed in /lib, the last directory dasbind's +# library search tries, so the test names it by bare file name everywhere. +if(NOT CMAKE_SIZEOF_VOID_P EQUAL 8 OR EMSCRIPTEN OR ANDROID OR IOS) + return() +endif() + +add_library(dasbind_probe SHARED probe/dasbind_probe.cpp) +set(_dasbind_probe_dir "${PROJECT_SOURCE_DIR}/lib") +set_target_properties(dasbind_probe PROPERTIES + PREFIX "" + LIBRARY_OUTPUT_DIRECTORY "${_dasbind_probe_dir}" + RUNTIME_OUTPUT_DIRECTORY "${_dasbind_probe_dir}" + LIBRARY_OUTPUT_DIRECTORY_DEBUG "${_dasbind_probe_dir}" + LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL "${_dasbind_probe_dir}" + LIBRARY_OUTPUT_DIRECTORY_RELEASE "${_dasbind_probe_dir}" + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO "${_dasbind_probe_dir}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${_dasbind_probe_dir}" + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${_dasbind_probe_dir}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${_dasbind_probe_dir}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${_dasbind_probe_dir}" +) diff --git a/tests/dasbind/_reapply_fixture.das b/tests/dasbind/_reapply_fixture.das new file mode 100644 index 0000000000..06c3425f7f --- /dev/null +++ b/tests/dasbind/_reapply_fixture.das @@ -0,0 +1,13 @@ +options gen2 +options no_aot + +require dasbind + +[extern(cdecl, name="dasbind_probe_stack14", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_stack14(a0 : int8; a1 : int16; a2 : int; a3 : int64; a4 : float; a5 : double; a6 : int; + a7 : int8; a8 : int8; a9 : int16; a10 : int; a11 : int64; a12 : bool; a13 : int) : int64 { return 0l; } + +[export] +def main { + probe_stack14(int8(1), int16(2), 3, 4l, 0.5, 0.25lf, 6, int8(7), int8(8), int16(9), 10, 11l, true, 13) +} diff --git a/tests/dasbind/probe/dasbind_probe.cpp b/tests/dasbind/probe/dasbind_probe.cpp new file mode 100644 index 0000000000..fc3fc5799b --- /dev/null +++ b/tests/dasbind/probe/dasbind_probe.cpp @@ -0,0 +1,54 @@ +#include + +#if defined(_WIN32) +#define DASBIND_PROBE_API extern "C" __declspec(dllexport) +#else +#define DASBIND_PROBE_API extern "C" __attribute__((visibility("default"))) +#endif + +DASBIND_PROBE_API double dasbind_probe_dbl_at45 ( int64_t a0, int64_t a1, int64_t a2, int64_t a3, double a4, double a5 ) { + return double(a0) + double(a1)*10 + double(a2)*100 + double(a3)*1000 + a4*2 + a5*3; +} + +DASBIND_PROBE_API double dasbind_probe_dbl_at6 ( int64_t a0, int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, double a6, int64_t a7 ) { + return double(a0) + double(a1)*10 + double(a2)*100 + double(a3)*1000 + double(a4)*10000 + double(a5)*100000 + a6*2 + double(a7)*1000000; +} + +DASBIND_PROBE_API double dasbind_probe_dbl_at67 ( int64_t a0, int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, double a6, double a7 ) { + return double(a0) + double(a1)*10 + double(a2)*100 + double(a3)*1000 + double(a4)*10000 + double(a5)*100000 + a6*2 + a7*3; +} + +DASBIND_PROBE_API int64_t dasbind_probe_regs7 ( int8_t a0, int16_t a1, int32_t a2, int64_t a3, float a4, double a5, int32_t a6 ) { + return a0 + a1*10 + a2*100 + a3*1000 + int64_t(a4*4) + int64_t(a5*8) + int64_t(a6)*7; +} + +DASBIND_PROBE_API int64_t dasbind_probe_stack14 ( int8_t a0, int16_t a1, int32_t a2, int64_t a3, float a4, double a5, int32_t a6, + int8_t a7, int8_t a8, int16_t a9, int32_t a10, int64_t a11, bool a12, int32_t a13 ) { + return a0 + a1*10 + a2*100 + a3*1000 + int64_t(a4*4) + int64_t(a5*8) + int64_t(a6)*7 + + a7*11 + a8*13 + a9*17 + int64_t(a10)*19 + a11*23 + (a12 ? 29 : 0) + int64_t(a13)*31; +} + +DASBIND_PROBE_API float dasbind_probe_stack_narrow ( float a0, float a1, float a2, float a3, float a4, float a5, float a6, float a7, + int64_t a8, int64_t a9, int64_t a10, int64_t a11, int64_t a12, int64_t a13, int64_t a14, int64_t a15, + int8_t a16, int16_t a17, float a18, int32_t a19 ) { + return a0*1 + a1*2 + a2*3 + a3*4 + a4*5 + a5*6 + a6*7 + a7*8 + + float(a8*9 + a9*10 + a10*11 + a11*12 + a12*13 + a13*14 + a14*15 + a15*16) + + float(a16)*17 + float(a17)*18 + a18*19 + float(a19)*20; +} + +DASBIND_PROBE_API int64_t dasbind_probe_ref_short ( const float * r0, int64_t a1, const int32_t * r2 ) { + return int64_t(*r0 * 2) + a1*3 + int64_t(*r2)*5; +} + +DASBIND_PROBE_API int64_t dasbind_probe_ref_args ( const float * r0, int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, int64_t a6, int64_t a7, const int32_t * r8 ) { + return int64_t(*r0 * 2) + a1*3 + a2*4 + a3*5 + a4*6 + a5*7 + a6*8 + a7*9 + int64_t(*r8)*10; +} + +DASBIND_PROBE_API const float * dasbind_probe_ref_result ( int64_t a0, int64_t a1, int64_t a2, int64_t a3, int64_t a4, int64_t a5, int64_t a6, const float * src ) { + return (a0 + a1 + a2 + a3 + a4 + a5 + a6) == 28 ? src : nullptr; +} + +DASBIND_PROBE_API double dasbind_probe_stack_dbl_result ( double a0, int64_t a1, int64_t a2, int64_t a3, int64_t a4, + int64_t a5, int64_t a6, int64_t a7, int64_t a8, int32_t a9 ) { + return a0 + double(a1 + a2 + a3 + a4 + a5 + a6 + a7 + a8) + double(a9)*2; +} diff --git a/tests/dasbind/test_extern_abi.das b/tests/dasbind/test_extern_abi.das new file mode 100644 index 0000000000..7071b03a35 --- /dev/null +++ b/tests/dasbind/test_extern_abi.das @@ -0,0 +1,97 @@ +options gen2 +options no_aot +options rtti = true +options strict_smart_pointers = false + +require dastest/testing_boost public +require dasbind +require daslib/ast +require daslib/rtti + +//! every probe sums its arguments under distinct multipliers, so one argument landing in the +//! wrong register or stack slot changes the total; the library is tests/dasbind/probe + +//! declares this file's probe_stack14 again, so compiling it re-applies the extern in the process +let REAPPLY_FIXTURE = "{get_das_root()}/tests/dasbind/_reapply_fixture.das" + +[extern(cdecl, name="dasbind_probe_dbl_at45", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_dbl_at45(a0, a1, a2, a3 : int64; a4, a5 : double) : double { return 0.0lf; } + +[extern(cdecl, name="dasbind_probe_dbl_at6", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_dbl_at6(a0, a1, a2, a3, a4, a5 : int64; a6 : double; a7 : int64) : double { return 0.0lf; } + +[extern(cdecl, name="dasbind_probe_dbl_at67", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_dbl_at67(a0, a1, a2, a3, a4, a5 : int64; a6, a7 : double) : double { return 0.0lf; } + +[extern(cdecl, name="dasbind_probe_regs7", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_regs7(a0 : int8; a1 : int16; a2 : int; a3 : int64; a4 : float; a5 : double; a6 : int) : int64 { return 0l; } + +[extern(cdecl, name="dasbind_probe_stack14", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_stack14(a0 : int8; a1 : int16; a2 : int; a3 : int64; a4 : float; a5 : double; a6 : int; + a7 : int8; a8 : int8; a9 : int16; a10 : int; a11 : int64; a12 : bool; a13 : int) : int64 { return 0l; } + +[extern(cdecl, name="dasbind_probe_stack_narrow", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_stack_narrow(a0, a1, a2, a3, a4, a5, a6, a7 : float; a8, a9, a10, a11, a12, a13, a14, a15 : int64; + a16 : int8; a17 : int16; a18 : float; a19 : int) : float { return 0.0; } + +[extern(cdecl, name="dasbind_probe_ref_short", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_ref_short(r0 : float const&; a1 : int64; r2 : int const&) : int64 { return 0l; } + +[extern(cdecl, name="dasbind_probe_ref_args", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_ref_args(r0 : float const&; a1, a2, a3, a4, a5, a6, a7 : int64; r8 : int const&) : int64 { return 0l; } + +[extern(cdecl, name="dasbind_probe_ref_result", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_ref_result(a0, a1, a2, a3, a4, a5, a6 : int64; src : float const&) : float const& { return src; } + +[extern(cdecl, name="dasbind_probe_stack_dbl_result", windows_library="dasbind_probe", linux_library="dasbind_probe.so", macos_library="dasbind_probe.dylib")] +def probe_stack_dbl_result(a0 : double; a1, a2, a3, a4, a5, a6, a7, a8 : int64; a9 : int) : double { return 0.0lf; } + +[test] +def test_extern_argument_passing(t : T?) { + t |> run("doubles in positions 4 and 5 reach the callee's float registers") @(t : T?) { + t |> equal(probe_dbl_at45(1l, 2l, 3l, 4l, 1.5lf, 2.5lf), 4331.5lf) + } + t |> run("a double in position 6, followed by an integer so no register residue can stand in") @(t : T?) { + t |> equal(probe_dbl_at6(1l, 2l, 3l, 4l, 5l, 6l, 1.5lf, 7l), 7654324.0lf) + } + t |> run("doubles in positions 6 and 7") @(t : T?) { + t |> equal(probe_dbl_at67(1l, 2l, 3l, 4l, 5l, 6l, 1.5lf, 2.5lf), 654331.5lf) + } + t |> run("seven mixed-width arguments, all in registers") @(t : T?) { + t |> equal(probe_regs7(int8(1), int16(2), 3, 4l, 0.5, 0.25lf, 6), 4367l) + } + t |> run("fourteen mixed-width arguments, the tail on the stack") @(t : T?) { + t |> equal(probe_stack14(int8(1), int16(2), 3, 4l, 0.5, 0.25lf, 6, int8(7), int8(8), int16(9), 10, 11l, true, 13), 5576l) + } + t |> run("eight floats and eight integers fill the registers; int8, int16, float and int land on the stack; float result") @(t : T?) { + t |> equal(probe_stack_narrow(0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8l, 9l, 10l, 11l, 12l, 13l, 14l, 15l, int8(16), int16(17), 18.5, 19), 2687.5) + } + t |> run("a double result from a call with a stack argument") @(t : T?) { + t |> equal(probe_stack_dbl_result(0.5lf, 1l, 2l, 3l, 4l, 5l, 6l, 7l, 8l, 9), 54.5lf) + } + t |> run("a float reference is a pointer, not a float, in the wrapper table's mask") @(t : T?) { + let fv = 1.5 + let iv = 7 + t |> equal(probe_ref_short(fv, 4l, iv), 50l) + } + t |> run("a float reference rides an integer register and an int reference an 8-byte stack slot") @(t : T?) { + let fv = 1.5 + let iv = 7 + t |> equal(probe_ref_args(fv, 1l, 2l, 3l, 4l, 5l, 6l, 7l, iv), 269l) + } + t |> run("a reference result comes back in the integer register, whatever it points to") @(t : T?) { + let fv = 1.5 + t |> equal(probe_ref_result(1l, 2l, 3l, 4l, 5l, 6l, 7l, fv), 1.5) + } + t |> run("the call survives the extern being applied again by another program in the process") @(t : T?) { + var inscope access <- make_file_access("") + using() $(var mg : ModuleGroup) { + using() $(var cop : CodeOfPolicies) { + compile_file(REAPPLY_FIXTURE, access, unsafe(addr(mg)), cop) $(ok, program, issues) { + t |> success(ok, "the fixture compiles: {issues}") + } + } + } + t |> equal(probe_stack14(int8(1), int16(2), 3, 4l, 0.5, 0.25lf, 6, int8(7), int8(8), int16(9), 10, 11l, true, 13), 5576l) + } +}