From 2b56889119dec5d1efe8be6d8c44529bad6dc893 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 3 Aug 2026 09:44:03 +0900 Subject: [PATCH 1/4] jit: assert the float funcbox invariant and correct the interleaving note `residual_call_float_canonical_via_target_with_effect_info` bakes `target.concrete_ptr` as the funcbox that `executor.rs`'s `Type::Float` arm reads back through an `extern "C" fn(..) -> f64` ABI. That is correct only while `concrete_ptr` is the raw callee; the macro's `_concrete` wrapper has an `-> i64` signature carrying `f64::to_bits`. Only the `*_float_wrapped` call policies mint that divergence and no crate declares one, so state the invariant as a `debug_assert_eq!` at the bake site instead of leaving it resting on that absence. `call_float_function`'s note claimed no interleaved float-returning helper exists. `jit_math_ldexp_raw(f64, i64) -> f64` is one, minted with `arg_types = [Float, Int]` in the walker specializer. Record the actual reason it does not reach this seam: it is recorded onto the trace via `TraceCtx::call_float_typed_with_effect` and consumed by the backends and `bh_call_f_by_classes`, while this seam is fed from the jitcode descr pool. Add tests covering the interleaving refusal and the accepted `[Int, Float]` order. Assisted-by: Claude --- .../majit-metainterp/src/jitcode/assembler.rs | 16 ++++++ .../majit-metainterp/src/pyjitpl/dispatch.rs | 49 +++++++++++++++++-- 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/majit/majit-metainterp/src/jitcode/assembler.rs b/majit/majit-metainterp/src/jitcode/assembler.rs index 6a2abe56bdc..eb9290d46c4 100644 --- a/majit/majit-metainterp/src/jitcode/assembler.rs +++ b/majit/majit-metainterp/src/jitcode/assembler.rs @@ -3652,6 +3652,22 @@ impl JitCodeBuilder { expected RuntimeBhDescr::Call, got {other:?}" ), }; + // The funcbox baked below is read back by `executor.rs`'s `Type::Float` + // arm through an `extern "C" fn(..) -> f64` ABI, i.e. out of the + // floating-point return register. That is right for a raw callee + // address — `add_fn_ptr(ptr)` is `add_call_target(ptr, ptr)` — and + // wrong for a `_concrete` wrapper, which `#[jit_module]` gives an + // `-> i64` signature carrying `f64::to_bits`. Only the macro's + // `*_float_wrapped` call policies mint that divergence, and no crate + // declares one; assert the invariant here so the first declaration + // trips a build rather than silently stamping a float read out of the + // integer return register. + debug_assert_eq!( + target.trace_ptr, target.concrete_ptr, + "float residual call bakes concrete_ptr as its funcbox; a distinct \ + concrete target is the i64-packing `_concrete` wrapper and would be \ + read from the wrong return register" + ); let concrete_ptr = target.concrete_ptr as i64; let effect_info = resolve_call_release_gil_target(effect_info, target.concrete_ptr, target.save_err); diff --git a/majit/majit-metainterp/src/pyjitpl/dispatch.rs b/majit/majit-metainterp/src/pyjitpl/dispatch.rs index b0d00a59fa1..48a4fba226c 100644 --- a/majit/majit-metainterp/src/pyjitpl/dispatch.rs +++ b/majit/majit-metainterp/src/pyjitpl/dispatch.rs @@ -8468,9 +8468,18 @@ pub fn call_int_function(func_ptr: *const (), args: &[i64]) -> i64 { /// `descr.py:598-612 create_call_stub` generates a stub carrying the descr's /// real `arg_classes` signature; the convergence path for pyre is the libffi /// dispatch already recorded at `call_stub.rs` `dispatch_arity_body!`'s -/// catch-all arm. Every float-returning helper in the tree today is -/// non-interleaved (`jit_bigint_to_f64_or_inf(ref) -> f64`, -/// `jit_float_abs(f64) -> f64`, `jit_float_fmod(f64, f64) -> f64`). +/// catch-all arm. +/// +/// An interleaved float-returning helper does exist — +/// `jit_math_ldexp_raw(f64, i64) -> f64`, whose descr is minted with +/// `arg_types = [Float, Int]` in `pyre-jit-trace`'s `specialize.rs`. It does +/// not reach here: the walker specializer records it straight onto the trace +/// via `TraceCtx::call_float_typed_with_effect` and obtains its concrete result +/// by calling the builtin separately, so the descr is consumed by the compiled +/// backends and by the blackhole's `bh_call_f_by_classes` (which buckets by +/// `arg_classes` with no such restriction). This seam is fed only from the +/// jitcode descr pool, and no descr there places an integer/ref slot after a +/// float one. pub fn call_float_function(func_ptr: *const (), args: &[i64], arg_types: &[Type]) -> f64 { // Where a backend cannot build a `call_indirect` whose type matches the // callee's real signature (wasm32), route through the host trampoline, @@ -8914,6 +8923,40 @@ mod tests { use crate::virtualizable::VirtualizableInfo; use majit_ir::Type; + extern "C" fn scale_f64(x: f64, k: i64) -> f64 { + x * k as f64 + } + + /// The bucketing `call_float_function` performs is register-preserving + /// where the two register files are filled independently (SysV, AAPCS) and + /// not where the argument slots are positional (Microsoft x64), so an + /// integer/ref parameter following a float one is refused rather than + /// dispatched differently per target. Locks that contract in place. + #[test] + #[should_panic(expected = "cannot be bucketed")] + fn interleaved_int_after_float_is_refused() { + call_float_function( + scale_f64 as *const (), + &[2.5_f64.to_bits() as i64, 3], + &[Type::Float, Type::Int], + ); + } + + /// The same argument classes in non-interleaved order are dispatchable: + /// only the ordering is refused, not the presence of both classes. + #[test] + fn float_after_int_dispatches() { + extern "C" fn scale_swapped(k: i64, x: f64) -> f64 { + x * k as f64 + } + let result = call_float_function( + scale_swapped as *const (), + &[3, 2.5_f64.to_bits() as i64], + &[Type::Int, Type::Float], + ); + assert_eq!(result, 7.5, "scale_swapped(3, 2.5) == 7.5"); + } + #[derive(Default)] struct DummySym; From a3aa4515a9bc5bfb0b4f49eb785ffe9023160b4f Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 3 Aug 2026 14:01:28 +0900 Subject: [PATCH 2/4] jit: dispatch residual calls in arg_classes declaration order MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `dispatch_arity_body!` recovered the callee signature from the bucket counts `(n_int, n_float)`, so a callee declared `fn(f64, i64)` was dispatched as `fn(i64, f64)`. `call_float_function` refused such a signature outright. `descr.py:574 create_call_stub` builds the call as `", ".join([process(c) for c in self.arg_classes])`, and `process(c)` walks `arg_classes` in declaration order pulling the next item out of the matching bank; `descr.py:604-605` builds `FuncType(ARGS, RESULT)` over the same ordered list. Upstream buckets for transport and un-buckets before the machine call. `test_descr.py::test_call_stubs_2` runs `arg_classes == "fr"` through `call_stub_f`. There is no arity ceiling and no ordering restriction upstream. Replace the bucket-keyed table with a class-sequence-keyed one: `ArgClass` carries the two C-ABI register classes, and `dispatch_classes_body!` matches on the ordered class slice — every sequence of length 0..=5 plus the integer-only tail through 16, 74 arms. `collect_call_args` now returns the ordered class list and a positional argument list. `bh_call_{i,f,v}_dispatch` take `(classes, args)`; the dynasm and cranelift `bh_call_*` overrides and `call_float_function` pass them through. The float-after-int refusal in `call_float_function` is deleted. Add a port of `test_call_stubs_2` plus two further interleaved shapes. Assisted-by: Claude --- majit/majit-backend-cranelift/src/compiler.rs | 44 +- majit/majit-backend-dynasm/src/runner.rs | 35 +- majit/majit-backend/src/call_stub.rs | 1447 +++++++++++++---- .../majit-metainterp/src/pyjitpl/dispatch.rs | 86 +- 4 files changed, 1157 insertions(+), 455 deletions(-) diff --git a/majit/majit-backend-cranelift/src/compiler.rs b/majit/majit-backend-cranelift/src/compiler.rs index 9d48979d2f7..b4c7567624e 100644 --- a/majit/majit-backend-cranelift/src/compiler.rs +++ b/majit/majit-backend-cranelift/src/compiler.rs @@ -16808,18 +16808,9 @@ impl majit_backend::Backend for CraneliftBackend { /// llmodel.py:816 bh_call_i: ABI-correct dispatch. /// - /// ARM64/x86-64 C ABI assigns integer and float args to independent register - /// files (x0-x7 + d0-d7 on ARM64; rdi,rsi,… + xmm0-xmm7 on x86-64). - /// We construct `fn(ints…, floats…) -> i64` which places each group in the - /// correct register file regardless of their original interleaving order. - /// - /// llmodel.py:816-820 bh_call_i(func, args_i, args_r, args_f, calldescr) - /// calldescr.call_stub_i(func, args_i, args_r, args_f). - /// - /// On ARM64/x86-64, the C ABI assigns integer and floating-point args to - /// independent register files (x0-x7 / d0-d7 on ARM64; rdi,rsi,... / - /// xmm0-xmm7 on x86-64). So we can always construct the function pointer - /// as `fn(ints..., floats...) -> i64` and get the correct register layout. + /// Routes through `majit_backend::call_stub::bh_call_i_dispatch`, whose + /// signature is built in `arg_classes` declaration order to match + /// `descr.py:574` / `descr.py:604-605 create_call_stub`. fn bh_call_i( &self, func: i64, @@ -16831,15 +16822,13 @@ impl majit_backend::Backend for CraneliftBackend { if func == 0 { return 0; } - let (int_args, float_args) = majit_backend::call_stub::collect_call_args( + let (classes, args) = majit_backend::call_stub::collect_call_args( &calldescr.arg_classes, args_i, args_r, args_f, ); - unsafe { - majit_backend::call_stub::bh_call_i_dispatch(func as usize, &int_args, &float_args) - } + unsafe { majit_backend::call_stub::bh_call_i_dispatch(func as usize, &classes, &args) } } /// llmodel.py:818 bh_call_r: GcRef-returning parallel of `bh_call_i`. @@ -16859,19 +16848,18 @@ impl majit_backend::Backend for CraneliftBackend { if func == 0 { return majit_ir::GcRef::NULL; } - let (int_args, float_args) = majit_backend::call_stub::collect_call_args( + let (classes, args) = majit_backend::call_stub::collect_call_args( &calldescr.arg_classes, args_i, args_r, args_f, ); - let raw = unsafe { - majit_backend::call_stub::bh_call_i_dispatch(func as usize, &int_args, &float_args) - }; + let raw = + unsafe { majit_backend::call_stub::bh_call_i_dispatch(func as usize, &classes, &args) }; majit_ir::GcRef(raw as usize) } - /// llmodel.py:825 bh_call_f / descr.py:590-602 create_call_stub + /// llmodel.py:825 bh_call_f / descr.py:584-605 create_call_stub /// (`RESULT == lltype.Float`): route through the f64-typed /// dispatcher so the result lands in xmm0 / d0 rather than rax / /// x0. Without this override `bhimpl_residual_call_*_f` would @@ -16888,18 +16876,16 @@ impl majit_backend::Backend for CraneliftBackend { if func == 0 { return 0.0; } - let (int_args, float_args) = majit_backend::call_stub::collect_call_args( + let (classes, args) = majit_backend::call_stub::collect_call_args( &calldescr.arg_classes, args_i, args_r, args_f, ); - unsafe { - majit_backend::call_stub::bh_call_f_dispatch(func as usize, &int_args, &float_args) - } + unsafe { majit_backend::call_stub::bh_call_f_dispatch(func as usize, &classes, &args) } } - /// llmodel.py:834 bh_call_v / descr.py:590-602 create_call_stub + /// llmodel.py:834 bh_call_v / descr.py:590-605 create_call_stub /// (`RESULT == lltype.Void`): dispatch via the void-typed stub so /// the funcptr is transmuted to `extern "C" fn(...) -> ()`. Without /// this override the canonical `residual_call_*_v` walker would @@ -16913,7 +16899,7 @@ impl majit_backend::Backend for CraneliftBackend { args_f: Option<&[i64]>, calldescr: &majit_translate::jitcode::BhCallDescr, ) { - // llmodel.py:834 bh_call_v / descr.py:590-602 create_call_stub + // llmodel.py:834 bh_call_v / descr.py:590-605 create_call_stub // (`RESULT == lltype.Void`) parity: route through the void-typed // dispatcher so genuinely void C callees use the right C-ABI // signature instead of `extern "C" fn(...) -> i64` (which reads @@ -16921,14 +16907,14 @@ impl majit_backend::Backend for CraneliftBackend { if func == 0 { return; } - let (int_args, float_args) = majit_backend::call_stub::collect_call_args( + let (classes, args) = majit_backend::call_stub::collect_call_args( &calldescr.arg_classes, args_i, args_r, args_f, ); unsafe { - majit_backend::call_stub::bh_call_v_dispatch(func as usize, &int_args, &float_args); + majit_backend::call_stub::bh_call_v_dispatch(func as usize, &classes, &args); } } diff --git a/majit/majit-backend-dynasm/src/runner.rs b/majit/majit-backend-dynasm/src/runner.rs index 17b3f5cf6e2..87ee74a4af3 100644 --- a/majit/majit-backend-dynasm/src/runner.rs +++ b/majit/majit-backend-dynasm/src/runner.rs @@ -3341,11 +3341,9 @@ impl Backend for DynasmBackend { /// llmodel.py:816 bh_call_i: ABI-correct dispatch via the shared call stub. /// - /// On ARM64/x86-64 the C ABI assigns integer and float args to independent - /// register files, so a typed `extern "C" fn(I × ints, F × floats) -> i64` - /// transmute lands them correctly regardless of original interleaving. - /// Routes through `majit_backend::call_stub::bh_call_i_dispatch` which - /// owns the arity table previously embedded in cranelift's `compiler.rs`. + /// Routes through `majit_backend::call_stub::bh_call_i_dispatch`, whose + /// signature is built in `arg_classes` declaration order to match + /// `descr.py:574` / `descr.py:604-605 create_call_stub`. fn bh_call_i( &self, func: i64, @@ -3357,15 +3355,13 @@ impl Backend for DynasmBackend { if func == 0 { return 0; } - let (int_args, float_args) = majit_backend::call_stub::collect_call_args( + let (classes, args) = majit_backend::call_stub::collect_call_args( &calldescr.arg_classes, args_i, args_r, args_f, ); - unsafe { - majit_backend::call_stub::bh_call_i_dispatch(func as usize, &int_args, &float_args) - } + unsafe { majit_backend::call_stub::bh_call_i_dispatch(func as usize, &classes, &args) } } /// llmodel.py:818 bh_call_r: GcRef-returning parallel of `bh_call_i`. @@ -3385,19 +3381,18 @@ impl Backend for DynasmBackend { if func == 0 { return majit_ir::GcRef::NULL; } - let (int_args, float_args) = majit_backend::call_stub::collect_call_args( + let (classes, args) = majit_backend::call_stub::collect_call_args( &calldescr.arg_classes, args_i, args_r, args_f, ); - let raw = unsafe { - majit_backend::call_stub::bh_call_i_dispatch(func as usize, &int_args, &float_args) - }; + let raw = + unsafe { majit_backend::call_stub::bh_call_i_dispatch(func as usize, &classes, &args) }; majit_ir::GcRef(raw as usize) } - /// llmodel.py:825 bh_call_f / descr.py:590-602 create_call_stub + /// llmodel.py:825 bh_call_f / descr.py:584-605 create_call_stub /// (`RESULT == lltype.Float`) parity: route through the f64-typed /// dispatcher so an f64-returning C callee delivers via xmm0 / d0 /// instead of rax / x0. Without this override @@ -3414,18 +3409,16 @@ impl Backend for DynasmBackend { if func == 0 { return 0.0; } - let (int_args, float_args) = majit_backend::call_stub::collect_call_args( + let (classes, args) = majit_backend::call_stub::collect_call_args( &calldescr.arg_classes, args_i, args_r, args_f, ); - unsafe { - majit_backend::call_stub::bh_call_f_dispatch(func as usize, &int_args, &float_args) - } + unsafe { majit_backend::call_stub::bh_call_f_dispatch(func as usize, &classes, &args) } } - /// llmodel.py:834 bh_call_v / descr.py:590-602 create_call_stub + /// llmodel.py:834 bh_call_v / descr.py:590-605 create_call_stub /// (`RESULT == lltype.Void`) parity: dispatch the funcptr through /// the void-typed `bh_call_v_dispatch` so a genuinely void C callee /// is called with the right C-ABI signature. Re-routing through @@ -3446,14 +3439,14 @@ impl Backend for DynasmBackend { if func == 0 { return; } - let (int_args, float_args) = majit_backend::call_stub::collect_call_args( + let (classes, args) = majit_backend::call_stub::collect_call_args( &calldescr.arg_classes, args_i, args_r, args_f, ); unsafe { - majit_backend::call_stub::bh_call_v_dispatch(func as usize, &int_args, &float_args); + majit_backend::call_stub::bh_call_v_dispatch(func as usize, &classes, &args); } } diff --git a/majit/majit-backend/src/call_stub.rs b/majit/majit-backend/src/call_stub.rs index 0e7f8d5fc55..7f73d3f75b7 100644 --- a/majit/majit-backend/src/call_stub.rs +++ b/majit/majit-backend/src/call_stub.rs @@ -1,244 +1,1033 @@ //! C-ABI call stub dispatch shared between backends. //! -//! `bh_call_i_dispatch` mirrors `rpython/jit/backend/llsupport/llmodel.py:816 call_stub_i` — -//! the arity-table that materializes a typed `extern "C" fn` from a raw funcptr -//! and forwards integer + float register files independently per the SysV / -//! AAPCS C ABI. +//! `bh_call_i_dispatch` mirrors `rpython/jit/backend/llsupport/llmodel.py:816 call_stub_i`: +//! it materializes a typed `extern "C" fn` from a raw funcptr and forwards +//! arguments in the calldescr declaration order preserved by `arg_classes`. +//! `bh_call_f_dispatch` and `bh_call_v_dispatch` are the float-returning and +//! void-returning parallels for `llmodel.py:825 bh_call_f` and +//! `llmodel.py:834 bh_call_v`. //! -//! `bh_call_v_dispatch` is the void-return parallel of `bh_call_i_dispatch`, -//! mirroring `rpython/jit/backend/llsupport/llmodel.py:834 bh_call_v` / -//! `descr.py:598-612 create_call_stub` where `RESULT == lltype.Void` produces -//! a stub whose generated function signature returns nothing. Using a real -//! `extern "C" fn(...) -> ()` transmute matches the C ABI of genuinely void -//! callees instead of reading whatever rax/x0 happens to carry. -//! -//! `bh_call_v_dispatch` mirrors `bh_call_i_dispatch`'s arity table verbatim -//! through the shared `dispatch_arity_body!` macro so callers see identical -//! arity coverage regardless of return type. +//! Upstream `descr.py:574` builds the generated call expression by walking +//! `self.arg_classes`, and `descr.py:604-605` builds `FuncType(ARGS, RESULT)` +//! from that same ordered class list. Matching that shape is required by the +//! Microsoft x64 positional-slot convention and is also correct under SysV and +//! AAPCS. -/// Arity-table body shared by `bh_call_i_dispatch` and `bh_call_v_dispatch`. -/// `$ret` plugs into both the function-pointer signature and the dispatch -/// function's return type so each unit-arm just evaluates `f(...)` and -/// returns the produced value (`i64` or `()`). -/// -/// `descr.py:598-612 create_call_stub` parity: a single ARGS×RESULT shape -/// per dispatch; we enumerate the same shape twice (once per RESULT) instead -/// of generating one stub per descriptor. +/// `descr.py:556-570 TYPE()` collapsed to the two C-ABI register classes the +/// dispatch table can express: `'i'`, `'r'` and `'L'` (`lltype.Signed`, +/// `llmemory.GCREF`, `lltype.SignedLongLong`) all pass in an integer register; +/// `'f'` (`lltype.Float`) passes in a floating-point register. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum ArgClass { + Int, + Float, +} + +/// Class-sequence table shared by `bh_call_i_dispatch`, `bh_call_f_dispatch`, +/// and `bh_call_v_dispatch`. `$ret` plugs into both the function-pointer +/// signature and the dispatch function's return type. /// -/// The signature is recovered from the *bucketed* `(int, float)` arity, so a -/// callee declaring `fn(f64, i64)` is dispatched as `fn(i64, f64)`. That is -/// ABI-preserving only where integer and floating-point parameters are -/// assigned from two independent register files in their own relative order: -/// SysV (rdi.. / xmm0..) and AAPCS (x0.. / d0..). The Microsoft x64 -/// convention assigns the first four arguments *by position* — argument 0 -/// takes rcx or xmm0 depending on its type, argument 1 takes rdx or xmm1 — -/// so there the bucketing would move both arguments to the wrong register. -/// Callers that can hold an interleaved signature must reject it before -/// reaching here; the general fix is the libffi dispatch named in the -/// catch-all arm below, which also removes the arity ceiling. -macro_rules! dispatch_arity_body { - ($func:ident, $int_args:ident, $float_args:ident, $ret:ty) => {{ +/// `descr.py:574` / `descr.py:604-605 create_call_stub` parity: the signature +/// is built in `arg_classes` declaration order, so `fn(f64, i64)` is dispatched +/// as `extern "C" fn(f64, i64)` rather than a class-blind +/// `extern "C" fn(i64, f64)`. That preserves SysV/AAPCS register-file order +/// and the Microsoft x64 positional argument slots alike. +macro_rules! dispatch_classes_body { + ($func:ident, $classes:ident, $args:ident, $ret:ty) => {{ type I = i64; type F = f64; - match ($int_args.len(), $float_args.len()) { - // No float args — integer-only calls (0..=16 to match - // `pyjitpl/dispatch.rs::call_int_function` / - // `call_void_function` MAX_HOST_CALL_ARITY = 16). - (0, 0) => { + assert_eq!( + $classes.len(), + $args.len(), + "bh_call dispatch: class sequence and positional arg list length differ" + ); + match $classes { + [] => { let f: unsafe extern "C" fn() -> $ret = std::mem::transmute($func); f() } - (1, 0) => { + [ArgClass::Int] => { let f: unsafe extern "C" fn(I) -> $ret = std::mem::transmute($func); - f($int_args[0]) + f($args[0]) } - (2, 0) => { + [ArgClass::Float] => { + let f: unsafe extern "C" fn(F) -> $ret = std::mem::transmute($func); + f(f64::from_bits($args[0] as u64)) + } + [ArgClass::Int, ArgClass::Int] => { let f: unsafe extern "C" fn(I, I) -> $ret = std::mem::transmute($func); - f($int_args[0], $int_args[1]) + f($args[0], $args[1]) + } + [ArgClass::Int, ArgClass::Float] => { + let f: unsafe extern "C" fn(I, F) -> $ret = std::mem::transmute($func); + f($args[0], f64::from_bits($args[1] as u64)) + } + [ArgClass::Float, ArgClass::Int] => { + let f: unsafe extern "C" fn(F, I) -> $ret = std::mem::transmute($func); + f(f64::from_bits($args[0] as u64), $args[1]) } - (3, 0) => { + [ArgClass::Float, ArgClass::Float] => { + let f: unsafe extern "C" fn(F, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + ) + } + [ArgClass::Int, ArgClass::Int, ArgClass::Int] => { let f: unsafe extern "C" fn(I, I, I) -> $ret = std::mem::transmute($func); - f($int_args[0], $int_args[1], $int_args[2]) + f($args[0], $args[1], $args[2]) + } + [ArgClass::Int, ArgClass::Int, ArgClass::Float] => { + let f: unsafe extern "C" fn(I, I, F) -> $ret = std::mem::transmute($func); + f($args[0], $args[1], f64::from_bits($args[2] as u64)) + } + [ArgClass::Int, ArgClass::Float, ArgClass::Int] => { + let f: unsafe extern "C" fn(I, F, I) -> $ret = std::mem::transmute($func); + f($args[0], f64::from_bits($args[1] as u64), $args[2]) + } + [ArgClass::Int, ArgClass::Float, ArgClass::Float] => { + let f: unsafe extern "C" fn(I, F, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + ) + } + [ArgClass::Float, ArgClass::Int, ArgClass::Int] => { + let f: unsafe extern "C" fn(F, I, I) -> $ret = std::mem::transmute($func); + f(f64::from_bits($args[0] as u64), $args[1], $args[2]) + } + [ArgClass::Float, ArgClass::Int, ArgClass::Float] => { + let f: unsafe extern "C" fn(F, I, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + f64::from_bits($args[2] as u64), + ) + } + [ArgClass::Float, ArgClass::Float, ArgClass::Int] => { + let f: unsafe extern "C" fn(F, F, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + $args[2], + ) + } + [ArgClass::Float, ArgClass::Float, ArgClass::Float] => { + let f: unsafe extern "C" fn(F, F, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + ) } - (4, 0) => { + [ArgClass::Int, ArgClass::Int, ArgClass::Int, ArgClass::Int] => { let f: unsafe extern "C" fn(I, I, I, I) -> $ret = std::mem::transmute($func); - f($int_args[0], $int_args[1], $int_args[2], $int_args[3]) + f($args[0], $args[1], $args[2], $args[3]) + } + [ArgClass::Int, ArgClass::Int, ArgClass::Int, ArgClass::Float] => { + let f: unsafe extern "C" fn(I, I, I, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + $args[2], + f64::from_bits($args[3] as u64), + ) + } + [ArgClass::Int, ArgClass::Int, ArgClass::Float, ArgClass::Int] => { + let f: unsafe extern "C" fn(I, I, F, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + f64::from_bits($args[2] as u64), + $args[3], + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, I, F, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + ) + } + [ArgClass::Int, ArgClass::Float, ArgClass::Int, ArgClass::Int] => { + let f: unsafe extern "C" fn(I, F, I, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + $args[2], + $args[3], + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, F, I, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + $args[2], + f64::from_bits($args[3] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, F, F, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + $args[3], + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, F, F, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + ) + } + [ArgClass::Float, ArgClass::Int, ArgClass::Int, ArgClass::Int] => { + let f: unsafe extern "C" fn(F, I, I, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + $args[2], + $args[3], + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, I, I, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + $args[2], + f64::from_bits($args[3] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, I, F, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + f64::from_bits($args[2] as u64), + $args[3], + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, I, F, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, F, I, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + $args[2], + $args[3], + ) } - (5, 0) => { + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, F, I, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + $args[2], + f64::from_bits($args[3] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, F, F, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + $args[3], + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, F, F, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I) -> $ret = std::mem::transmute($func); + f($args[0], $args[1], $args[2], $args[3], $args[4]) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, I, I, I, F) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], + $args[0], + $args[1], + $args[2], + $args[3], + f64::from_bits($args[4] as u64), ) } - (6, 0) => { - let f: unsafe extern "C" fn(I, I, I, I, I, I) -> $ret = std::mem::transmute($func); + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, I, I, F, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + $args[2], + f64::from_bits($args[3] as u64), + $args[4], + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, I, I, F, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + $args[2], + f64::from_bits($args[3] as u64), + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, I, F, I, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + f64::from_bits($args[2] as u64), + $args[3], + $args[4], + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, I, F, I, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + f64::from_bits($args[2] as u64), + $args[3], + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, I, F, F, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + $args[4], + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, I, F, F, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + $args[1], + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, F, I, I, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + $args[2], + $args[3], + $args[4], + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, F, I, I, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + $args[2], + $args[3], + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, F, I, F, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + $args[2], + f64::from_bits($args[3] as u64), + $args[4], + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, F, I, F, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + $args[2], + f64::from_bits($args[3] as u64), + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, F, F, I, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + $args[3], + $args[4], + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, F, F, I, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + $args[3], + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, F, F, F, I) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + $args[4], + ) + } + [ + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(I, F, F, F, F) -> $ret = std::mem::transmute($func); + f( + $args[0], + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, I, I, I, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + $args[2], + $args[3], + $args[4], + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, I, I, I, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + $args[2], + $args[3], + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, I, I, F, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + $args[2], + f64::from_bits($args[3] as u64), + $args[4], + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, I, I, F, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + $args[2], + f64::from_bits($args[3] as u64), + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, I, F, I, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + f64::from_bits($args[2] as u64), + $args[3], + $args[4], + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, I, F, I, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + f64::from_bits($args[2] as u64), + $args[3], + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, I, F, F, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + $args[4], + ) + } + [ + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, I, F, F, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + $args[1], + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, F, I, I, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + $args[2], + $args[3], + $args[4], + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, F, I, I, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + $args[2], + $args[3], + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, F, I, F, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + $args[2], + f64::from_bits($args[3] as u64), + $args[4], + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, F, I, F, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + $args[2], + f64::from_bits($args[3] as u64), + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, F, F, I, I) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + $args[3], + $args[4], + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, F, F, I, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + $args[3], + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(F, F, F, F, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + $args[4], ) } - (7, 0) => { + [ + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ArgClass::Float, + ] => { + let f: unsafe extern "C" fn(F, F, F, F, F) -> $ret = std::mem::transmute($func); + f( + f64::from_bits($args[0] as u64), + f64::from_bits($args[1] as u64), + f64::from_bits($args[2] as u64), + f64::from_bits($args[3] as u64), + f64::from_bits($args[4] as u64), + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { + let f: unsafe extern "C" fn(I, I, I, I, I, I) -> $ret = std::mem::transmute($func); + f($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], ) } - (8, 0) => { + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], ) } - (9, 0) => { + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], - $int_args[8], + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], + $args[8], ) } - (10, 0) => { + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], - $int_args[8], - $int_args[9], + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], + $args[8], $args[9], ) } - (11, 0) => { + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], - $int_args[8], - $int_args[9], - $int_args[10], + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], + $args[8], $args[9], $args[10], ) } - (12, 0) => { + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], - $int_args[8], - $int_args[9], - $int_args[10], - $int_args[11], - ) - } - (13, 0) => { + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], + $args[8], $args[9], $args[10], $args[11], + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], - $int_args[8], - $int_args[9], - $int_args[10], - $int_args[11], - $int_args[12], - ) - } - (14, 0) => { + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], + $args[8], $args[9], $args[10], $args[11], $args[12], + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I, I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], - $int_args[8], - $int_args[9], - $int_args[10], - $int_args[11], - $int_args[12], - $int_args[13], - ) - } - (15, 0) => { + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], + $args[8], $args[9], $args[10], $args[11], $args[12], $args[13], + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn(I, I, I, I, I, I, I, I, I, I, I, I, I, I, I) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], - $int_args[8], - $int_args[9], - $int_args[10], - $int_args[11], - $int_args[12], - $int_args[13], - $int_args[14], - ) - } - (16, 0) => { + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], + $args[8], $args[9], $args[10], $args[11], $args[12], $args[13], $args[14], + ) + } + [ + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ArgClass::Int, + ] => { let f: unsafe extern "C" fn( I, I, @@ -258,104 +1047,24 @@ macro_rules! dispatch_arity_body { I, ) -> $ret = std::mem::transmute($func); f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $int_args[4], - $int_args[5], - $int_args[6], - $int_args[7], - $int_args[8], - $int_args[9], - $int_args[10], - $int_args[11], - $int_args[12], - $int_args[13], - $int_args[14], - $int_args[15], - ) - } - // Float-only calls. - (0, 1) => { - let f: unsafe extern "C" fn(F) -> $ret = std::mem::transmute($func); - f($float_args[0]) - } - (0, 2) => { - let f: unsafe extern "C" fn(F, F) -> $ret = std::mem::transmute($func); - f($float_args[0], $float_args[1]) - } - (0, 3) => { - let f: unsafe extern "C" fn(F, F, F) -> $ret = std::mem::transmute($func); - f($float_args[0], $float_args[1], $float_args[2]) - } - (0, 4) => { - let f: unsafe extern "C" fn(F, F, F, F) -> $ret = std::mem::transmute($func); - f( - $float_args[0], - $float_args[1], - $float_args[2], - $float_args[3], - ) - } - // Mixed int + float calls. - (1, 1) => { - let f: unsafe extern "C" fn(I, F) -> $ret = std::mem::transmute($func); - f($int_args[0], $float_args[0]) - } - (2, 1) => { - let f: unsafe extern "C" fn(I, I, F) -> $ret = std::mem::transmute($func); - f($int_args[0], $int_args[1], $float_args[0]) - } - (1, 2) => { - let f: unsafe extern "C" fn(I, F, F) -> $ret = std::mem::transmute($func); - f($int_args[0], $float_args[0], $float_args[1]) - } - (2, 2) => { - let f: unsafe extern "C" fn(I, I, F, F) -> $ret = std::mem::transmute($func); - f($int_args[0], $int_args[1], $float_args[0], $float_args[1]) - } - (3, 1) => { - let f: unsafe extern "C" fn(I, I, I, F) -> $ret = std::mem::transmute($func); - f($int_args[0], $int_args[1], $int_args[2], $float_args[0]) - } - (4, 1) => { - let f: unsafe extern "C" fn(I, I, I, I, F) -> $ret = std::mem::transmute($func); - f( - $int_args[0], - $int_args[1], - $int_args[2], - $int_args[3], - $float_args[0], + $args[0], $args[1], $args[2], $args[3], $args[4], $args[5], $args[6], $args[7], + $args[8], $args[9], $args[10], $args[11], $args[12], $args[13], $args[14], + $args[15], ) } - (3, 2) => { - let f: unsafe extern "C" fn(I, I, I, F, F) -> $ret = std::mem::transmute($func); - f( - $int_args[0], - $int_args[1], - $int_args[2], - $float_args[0], - $float_args[1], - ) - } - (1, 3) => { - let f: unsafe extern "C" fn(I, F, F, F) -> $ret = std::mem::transmute($func); - f($int_args[0], $float_args[0], $float_args[1], $float_args[2]) - } - (ni, nf) => { + classes => { // TODO: upstream - // `rpython/jit/backend/llsupport/descr.py:590-602 - // create_call_stub` generates a per-calldescr stub at - // translation time so any (ni, nf) combination has a - // matching extern "C" signature. Rust has no - // translation-time codegen equivalent, so the dispatch - // is a hand-rolled arity table. Convergence path: - // wire libffi (or an ABI adapter) so any arity is - // dispatchable; until then, callees outside the - // table panic instead of silently corrupting registers. + // `rpython/jit/backend/llsupport/descr.py:574` / + // `descr.py:604-605 create_call_stub` generates a + // per-calldescr stub at translation time, so every class + // sequence has a matching extern "C" signature. Rust has no + // translation-time codegen equivalent here, so the dispatch + // is a hand-rolled class-sequence table. Convergence path: + // wire libffi (or an ABI adapter) so any sequence is + // dispatchable; until then, callees outside the table panic + // instead of silently corrupting registers. panic!( - "bh_call dispatch: unsupported arg combination ({ni} ints, {nf} floats); \ + "bh_call dispatch: unsupported arg class sequence {classes:?}; \ needs libffi for general dispatch" ); } @@ -363,57 +1072,55 @@ macro_rules! dispatch_arity_body { }}; } -/// llmodel.py:816 call_stub_i: ABI-correct dispatch with separate int/float -/// register files. On ARM64/x86-64, integer args go to x0-x7 / rdi,rsi,... and -/// float args go to d0-d7 / xmm0-xmm7 independently. +/// llmodel.py:816 call_stub_i: ABI-correct dispatch in calldescr declaration +/// order. /// -/// Safety: `func` must be a valid function pointer matching the described ABI -/// — i.e. `extern "C" fn(I × ints, F × floats) -> i64` for the (ints, floats) -/// arity recovered from `int_args.len()` / `float_args.len()`. -pub unsafe fn bh_call_i_dispatch(func: usize, int_args: &[i64], float_args: &[f64]) -> i64 { - unsafe { dispatch_arity_body!(func, int_args, float_args, i64) } +/// Safety: `func` must be a valid function pointer matching `classes`, i.e. an +/// `extern "C" fn(...) -> i64` whose parameter list is the same ordered +/// Int/Float sequence and whose float slots are carried in `args` as +/// `f64::to_bits`. +pub unsafe fn bh_call_i_dispatch(func: usize, classes: &[ArgClass], args: &[i64]) -> i64 { + unsafe { dispatch_classes_body!(func, classes, args, i64) } } /// llmodel.py:834 bh_call_v: void-typed parallel of `bh_call_i_dispatch`. /// -/// Safety: `func` must be a valid function pointer matching the described -/// ABI — i.e. `extern "C" fn(I × ints, F × floats) -> ()` for the recovered -/// (ints, floats) arity. `descr.py:590-602 create_call_stub` builds a real -/// void-returning stub for `RESULT == lltype.Void`; calling such a function -/// through an `i64`-returning transmute reads garbage from rax/x0, so the -/// canonical `BC_RESIDUAL_CALL_*_V` blackhole/trace path must use this -/// dispatcher rather than `bh_call_i_dispatch`. -pub unsafe fn bh_call_v_dispatch(func: usize, int_args: &[i64], float_args: &[f64]) { - unsafe { dispatch_arity_body!(func, int_args, float_args, ()) } +/// Safety: `func` must be a valid function pointer matching `classes`. +/// `descr.py:590-605 create_call_stub` builds a real void-returning stub for +/// `RESULT == lltype.Void`; calling such a function through an `i64`-returning +/// transmute reads garbage from rax/x0, so the canonical +/// `BC_RESIDUAL_CALL_*_V` blackhole/trace path must use this dispatcher. +pub unsafe fn bh_call_v_dispatch(func: usize, classes: &[ArgClass], args: &[i64]) { + unsafe { dispatch_classes_body!(func, classes, args, ()) } } /// llmodel.py:825 bh_call_f: f64-typed parallel of `bh_call_i_dispatch`. /// -/// Safety: `func` must be a valid function pointer matching the described -/// ABI — i.e. `extern "C" fn(I × ints, F × floats) -> f64` for the recovered -/// (ints, floats) arity. `descr.py:590-602 create_call_stub` generates a -/// real f64-returning stub for `RESULT == lltype.Float`; the C ABI returns -/// f64 in xmm0 / d0 rather than rax / x0, so an `i64`-typed transmute -/// would read uninitialized integer-bank state. -pub unsafe fn bh_call_f_dispatch(func: usize, int_args: &[i64], float_args: &[f64]) -> f64 { - unsafe { dispatch_arity_body!(func, int_args, float_args, f64) } +/// Safety: `func` must be a valid function pointer matching `classes`. +/// `descr.py:584-605 create_call_stub` generates a real f64-returning stub for +/// `RESULT == lltype.Float`; the C ABI returns f64 in xmm0 / d0 rather than +/// rax / x0, so an `i64`-typed transmute would read uninitialized integer-bank +/// state. +pub unsafe fn bh_call_f_dispatch(func: usize, classes: &[ArgClass], args: &[i64]) -> f64 { + unsafe { dispatch_classes_body!(func, classes, args, f64) } } -/// Bucket `args_i` / `args_r` / `args_f` slices into the (int, float) shape the -/// dispatch table expects, following `calldescr.arg_classes` order. +/// Build the C-ABI class sequence and positional argument list from +/// `args_i` / `args_r` / `args_f`, following `calldescr.arg_classes` order. /// /// `arg_classes` is the per-argument class string from /// `majit_translate::jitcode::BhCallDescr`. RPython -/// `rpython/jit/backend/llsupport/descr.py:545-571 create_call_stub`'s -/// `process(c)` defines the storage bank vs. C ABI register file mapping: +/// `rpython/jit/backend/llsupport/descr.py:545-574 create_call_stub`'s +/// `process(c)` walks the class string in declaration order and pulls the next +/// item out of the corresponding storage bank. /// -/// | class | storage bank | C ABI type | register file | -/// |-------|--------------|----------------------|---------------| -/// | `i` | `args_i` | `lltype.Signed` | int | -/// | `r` | `args_r` | `llmemory.GCREF` | int | -/// | `f` | `args_f` | `lltype.Float` | float | -/// | `L` | `args_f` | `lltype.SignedLongLong` | int | -/// | `S` | `args_i` | `lltype.SingleFloat` | float (f32) | +/// | class | storage bank | C ABI type | dispatch class | +/// |-------|--------------|-------------------------|----------------| +/// | `i` | `args_i` | `lltype.Signed` | Int | +/// | `r` | `args_r` | `llmemory.GCREF` | Int | +/// | `f` | `args_f` | `lltype.Float` | Float | +/// | `L` | `args_f` | `lltype.SignedLongLong` | Int | +/// | `S` | `args_i` | `lltype.SingleFloat` | Float (f32) | /// /// Note the asymmetry: `L` is stored in the float bank (PyPy `process('L')` /// rewrites `c = 'f'` for the storage lookup) yet passed in an integer @@ -429,22 +1136,16 @@ pub unsafe fn bh_call_f_dispatch(func: usize, int_args: &[i64], float_args: &[f6 /// today; reaching it requires a foreign-supplied calldescr (e.g. a /// build-time bincode embed loaded from RPython). /// -/// Mirrors `rpython/jit/backend/llsupport/descr.py:614-620 verify_types`: +/// Mirrors `rpython/jit/backend/llsupport/descr.py:616-620 verify_types`: /// the per-class counts in `arg_classes` must match the corresponding list /// length, and any unknown class is a codegen bug. -/// -/// The two returned buckets discard the interleaving `arg_classes` encodes; -/// see `dispatch_arity_body!` for which ABIs that reordering is sound on. This -/// function does not itself reject an interleaved `arg_classes` — the in-tree -/// calldescrs it is fed carry non-interleaved signatures, and rejecting here -/// would panic on calls that are ABI-correct under SysV/AAPCS. pub fn collect_call_args( arg_classes: &str, args_i: Option<&[i64]>, args_r: Option<&[i64]>, args_f: Option<&[i64]>, -) -> (Vec, Vec) { - // descr.py:614-620 verify_types parity: assert per-class counts. +) -> (Vec, Vec) { + // descr.py:616-620 verify_types parity: assert per-class counts. let count_i: usize = arg_classes .chars() .filter(|c| matches!(c, 'i' | 'S')) @@ -470,38 +1171,41 @@ pub fn collect_call_args( "BhCallDescr.verify_types: arg_classes={arg_classes:?} has {count_f} float slots, args_f has {len_f}" ); - let mut int_args: Vec = Vec::with_capacity(count_i + count_r); - let mut float_args: Vec = Vec::with_capacity(count_f); + let mut classes: Vec = Vec::with_capacity(arg_classes.len()); + let mut args: Vec = Vec::with_capacity(arg_classes.len()); let mut ii = 0usize; let mut ri = 0usize; let mut fi = 0usize; for c in arg_classes.chars() { match c { 'i' => { - int_args.push(args_i.expect("BhCallDescr.collect_call_args: args_i missing")[ii]); + classes.push(ArgClass::Int); + args.push(args_i.expect("BhCallDescr.collect_call_args: args_i missing")[ii]); ii += 1; } 'r' => { - int_args.push(args_r.expect("BhCallDescr.collect_call_args: args_r missing")[ri]); + classes.push(ArgClass::Int); + args.push(args_r.expect("BhCallDescr.collect_call_args: args_r missing")[ri]); ri += 1; } 'f' => { - let bits = args_f.expect("BhCallDescr.collect_call_args: args_f missing")[fi]; - float_args.push(f64::from_bits(bits as u64)); + classes.push(ArgClass::Float); + args.push(args_f.expect("BhCallDescr.collect_call_args: args_f missing")[fi]); fi += 1; } 'L' => { // descr.py:546-548 process('L'): storage bank = `args_f` // (PyPy rewrites `c = 'f'` for the lookup); FUNC parameter - // type = `lltype.SignedLongLong` → C `long long` → + // type = `lltype.SignedLongLong` -> C `long long` -> // 8-byte int dispatched in an integer register. - int_args.push(args_f.expect("BhCallDescr.collect_call_args: args_f missing")[fi]); + classes.push(ArgClass::Int); + args.push(args_f.expect("BhCallDescr.collect_call_args: args_f missing")[fi]); fi += 1; } 'S' => { // descr.py:551-552 process('S'): storage bank = `args_i` // (PyPy reads via `int2singlefloat(args_i[..])`); FUNC - // parameter type = `lltype.SingleFloat` → C `float` → + // parameter type = `lltype.SingleFloat` -> C `float` -> // 32-bit float dispatched in an xmm/d register. pyre's // dispatch table emits only `extern "C" fn(.., f64, ..)` // arms, so transmuting f32 through f64 would mismatch the @@ -519,7 +1223,7 @@ pub fn collect_call_args( ), } } - (int_args, float_args) + (classes, args) } /// Bucket `args_i` / `args_r` / `args_f` into a single positional list in @@ -571,20 +1275,15 @@ pub fn collect_call_args_positional( /// Dispatch a residual call described by `arg_classes`, picking the signature /// strategy the active backend supports. /// -/// `bh_call_*_dispatch` transmutes the funcptr to an `extern "C" fn` guessed -/// from the *bucketed* `(int, float)` arity. That is sound only on a C ABI that -/// tolerates a signature mismatch: SysV/AAPCS pass the surplus in registers the -/// callee ignores, and a `usize` parameter is register-width either way. wasm32 -/// has neither property — `call_indirect` type-checks the callee's declared -/// type on every call, and a pointer parameter is `i32` where the transmute -/// says `i64` — so a mistyped guess traps with `indirect call type mismatch` -/// instead of silently working. +/// `bh_call_*_dispatch` transmutes the funcptr to an `extern "C" fn` built +/// from `arg_classes` declaration order, matching `descr.py:574` / +/// `descr.py:604-605 create_call_stub`. wasm32 still cannot use that direct +/// transmute path: `call_indirect` type-checks the callee's declared type on +/// every call, and a pointer parameter is `i32` where the native table uses +/// `i64`, so a mistyped guess traps with `indirect call type mismatch`. /// /// Where a host trampoline is installed (`set_residual_host_call`, wasm32) the -/// call must therefore go through it with the *positional* argument list. -/// [`collect_call_args`] discards the interleaving `arg_classes` encodes, so -/// the choice cannot be recovered downstream: it belongs here, at the last -/// point that still holds `arg_classes`. +/// call must therefore go through it with the positional argument list. /// /// # Safety /// On the transmute path, `func` must match the ABI [`collect_call_args`] @@ -600,8 +1299,8 @@ pub unsafe fn bh_call_i_by_classes( let args = collect_call_args_positional(arg_classes, args_i, args_r, args_f); return hook(func, &args); } - let (int_args, float_args) = collect_call_args(arg_classes, args_i, args_r, args_f); - unsafe { bh_call_i_dispatch(func, &int_args, &float_args) } + let (classes, args) = collect_call_args(arg_classes, args_i, args_r, args_f); + unsafe { bh_call_i_dispatch(func, &classes, &args) } } /// f64-returning parallel of [`bh_call_i_by_classes`]. @@ -620,8 +1319,8 @@ pub unsafe fn bh_call_f_by_classes( // The trampoline returns an f64 callee result as its raw bits. return f64::from_bits(hook(func, &args) as u64); } - let (int_args, float_args) = collect_call_args(arg_classes, args_i, args_r, args_f); - unsafe { bh_call_f_dispatch(func, &int_args, &float_args) } + let (classes, args) = collect_call_args(arg_classes, args_i, args_r, args_f); + unsafe { bh_call_f_dispatch(func, &classes, &args) } } /// Result-discarding parallel of [`bh_call_i_by_classes`]. @@ -640,8 +1339,8 @@ pub unsafe fn bh_call_v_by_classes( let _ = hook(func, &args); return; } - let (int_args, float_args) = collect_call_args(arg_classes, args_i, args_r, args_f); - unsafe { bh_call_v_dispatch(func, &int_args, &float_args) } + let (classes, args) = collect_call_args(arg_classes, args_i, args_r, args_f); + unsafe { bh_call_v_dispatch(func, &classes, &args) } } /// A host-provided trampoline that performs a residual call by reflecting the @@ -675,3 +1374,59 @@ pub fn set_residual_host_call(hook: Option) { pub fn residual_host_call() -> Option { RESIDUAL_HOST_CALL.with(|c| c.get()) } + +#[cfg(test)] +mod tests { + use super::*; + + extern "C" fn f2(a: f64, b: *const i64) -> f64 { + a + unsafe { *b } as f64 + } + + extern "C" fn int_float_int(a: i64, b: f64, c: i64) -> i64 { + a + b as i64 * 10 + c * 100 + } + + extern "C" fn float_float_int(a: f64, b: f64, c: i64) -> f64 { + a + b * 10.0 + c as f64 * 100.0 + } + + /// Rust port of + /// `rpython/jit/backend/llsupport/test/test_descr.py::test_call_stubs_2`. + #[test] + fn call_stub_f_interleaved_float_ref_preserves_declaration_order() { + let b = [1_i64]; + let result = unsafe { + bh_call_f_dispatch( + f2 as *const () as usize, + &[ArgClass::Float, ArgClass::Int], + &[3.5_f64.to_bits() as i64, b.as_ptr() as i64], + ) + }; + assert_eq!(result, 4.5); + } + + #[test] + fn call_stub_i_interleaved_int_float_int_preserves_declaration_order() { + let result = unsafe { + bh_call_i_dispatch( + int_float_int as *const () as usize, + &[ArgClass::Int, ArgClass::Float, ArgClass::Int], + &[1, 2.0_f64.to_bits() as i64, 3], + ) + }; + assert_eq!(result, 321); + } + + #[test] + fn call_stub_f_interleaved_float_float_int_preserves_declaration_order() { + let result = unsafe { + bh_call_f_dispatch( + float_float_int as *const () as usize, + &[ArgClass::Float, ArgClass::Float, ArgClass::Int], + &[1.0_f64.to_bits() as i64, 2.0_f64.to_bits() as i64, 3], + ) + }; + assert_eq!(result, 321.0); + } +} diff --git a/majit/majit-metainterp/src/pyjitpl/dispatch.rs b/majit/majit-metainterp/src/pyjitpl/dispatch.rs index 48a4fba226c..a6505615759 100644 --- a/majit/majit-metainterp/src/pyjitpl/dispatch.rs +++ b/majit/majit-metainterp/src/pyjitpl/dispatch.rs @@ -8438,10 +8438,11 @@ pub fn call_int_function(func_ptr: *const (), args: &[i64]) -> i64 { /// /// The C ABI returns `f64` in xmm0 / d0, so a float-returning callee cannot be /// reached through [`call_int_function`]'s `-> i64` transmute — that reads the -/// integer return register. Float *arguments* have the same problem in the -/// other direction: they travel in the FP register file, so they are split out -/// here and handed to [`bh_call_f_dispatch`], which is pyre's stand-in for the -/// per-descr stub `descr.py:598-612 create_call_stub` generates upstream. +/// integer return register. Float *arguments* have the same problem in the +/// other direction: they travel in the FP register file, so the ordered C-ABI +/// class sequence is handed to [`bh_call_f_dispatch`], which is pyre's +/// stand-in for the per-descr stub `descr.py:574` / `descr.py:604-605 +/// create_call_stub` generates upstream. /// /// `args[i]` for a `Type::Float` slot carries the raw `f64::to_bits`, matching /// the `args_f` bank convention (`longlong` float storage). @@ -8452,34 +8453,7 @@ pub fn call_int_function(func_ptr: *const (), args: &[i64]) -> i64 { /// Neither can arrive here: this seam is fed by `descr.arg_types()`, and /// `CallDescr::arg_classes` (`majit-ir/src/descr.rs`) maps `Type` onto /// `'i'`/`'r'`/`'f'`/`'v'` exhaustively, so a `Type::Float` slot is always -/// class `'f'`. Teaching `Type` those classes must extend both sites. -/// -/// # Panics -/// `bh_call_f_dispatch` recovers the callee signature from the *bucketed* -/// `(ints, floats)` arity, i.e. `fn(I × ints, F × floats)`. Reordering an -/// interleaved signature into that shape is ABI-preserving only where the -/// integer and floating-point parameters are assigned from two independent -/// register files in their own relative order (SysV, AAPCS). The Microsoft -/// x64 convention instead assigns the first four arguments *by position* — -/// `fn(f64, i64)` takes xmm0/rdx where `fn(i64, f64)` takes rcx/xmm1 — so the -/// reordering would put both arguments in the wrong register there. An -/// interleaved signature is therefore rejected rather than dispatched -/// differently per target. Upstream needs no such restriction because -/// `descr.py:598-612 create_call_stub` generates a stub carrying the descr's -/// real `arg_classes` signature; the convergence path for pyre is the libffi -/// dispatch already recorded at `call_stub.rs` `dispatch_arity_body!`'s -/// catch-all arm. -/// -/// An interleaved float-returning helper does exist — -/// `jit_math_ldexp_raw(f64, i64) -> f64`, whose descr is minted with -/// `arg_types = [Float, Int]` in `pyre-jit-trace`'s `specialize.rs`. It does -/// not reach here: the walker specializer records it straight onto the trace -/// via `TraceCtx::call_float_typed_with_effect` and obtains its concrete result -/// by calling the builtin separately, so the descr is consumed by the compiled -/// backends and by the blackhole's `bh_call_f_by_classes` (which buckets by -/// `arg_classes` with no such restriction). This seam is fed only from the -/// jitcode descr pool, and no descr there places an integer/ref slot after a -/// float one. +/// class `'f'`. Teaching `Type` those classes must extend both sites. pub fn call_float_function(func_ptr: *const (), args: &[i64], arg_types: &[Type]) -> f64 { // Where a backend cannot build a `call_indirect` whose type matches the // callee's real signature (wasm32), route through the host trampoline, @@ -8487,25 +8461,26 @@ pub fn call_float_function(func_ptr: *const (), args: &[i64], arg_types: &[Type] if let Some(hook) = majit_backend::call_stub::residual_host_call() { return f64::from_bits(hook(func_ptr as usize, args) as u64); } - let mut int_args: Vec = Vec::with_capacity(args.len()); - let mut float_args: Vec = Vec::new(); - for (i, &a) in args.iter().enumerate() { - match arg_types.get(i) { - Some(Type::Float) => float_args.push(f64::from_bits(a as u64)), - _ => { - assert!( - float_args.is_empty(), - "call_float_function: an integer/ref parameter after a float \ - one cannot be bucketed into `bh_call_f_dispatch`'s \ - `fn(I.., F..)` signature without changing which register \ - each argument lands in on a positional-slot ABI" - ); - int_args.push(a); - } - } + assert!( + args.len() <= MAX_HOST_CALL_ARITY, + "unsupported JitCode float call arity {} (max {})", + args.len(), + MAX_HOST_CALL_ARITY + ); + let mut classes = [majit_backend::call_stub::ArgClass::Int; MAX_HOST_CALL_ARITY]; + for i in 0..args.len() { + classes[i] = match arg_types.get(i) { + Some(Type::Float) => majit_backend::call_stub::ArgClass::Float, + Some(Type::Int | Type::Ref) | None => majit_backend::call_stub::ArgClass::Int, + Some(Type::Void) => panic!("call_float_function: void argument at slot {i}"), + }; } unsafe { - majit_backend::call_stub::bh_call_f_dispatch(func_ptr as usize, &int_args, &float_args) + majit_backend::call_stub::bh_call_f_dispatch( + func_ptr as usize, + &classes[..args.len()], + args, + ) } } @@ -8927,23 +8902,16 @@ mod tests { x * k as f64 } - /// The bucketing `call_float_function` performs is register-preserving - /// where the two register files are filled independently (SysV, AAPCS) and - /// not where the argument slots are positional (Microsoft x64), so an - /// integer/ref parameter following a float one is refused rather than - /// dispatched differently per target. Locks that contract in place. #[test] - #[should_panic(expected = "cannot be bucketed")] - fn interleaved_int_after_float_is_refused() { - call_float_function( + fn interleaved_int_after_float_dispatches_in_declaration_order() { + let result = call_float_function( scale_f64 as *const (), &[2.5_f64.to_bits() as i64, 3], &[Type::Float, Type::Int], ); + assert_eq!(result, 7.5, "scale_f64(2.5, 3) == 7.5"); } - /// The same argument classes in non-interleaved order are dispatchable: - /// only the ordering is refused, not the presence of both classes. #[test] fn float_after_int_dispatches() { extern "C" fn scale_swapped(k: i64, x: f64) -> f64 { From 46ef114881e5610d6b225ca202d834c3662b8c6f Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 3 Aug 2026 14:01:42 +0900 Subject: [PATCH 3/4] jit: correct false claims in the float residual-call comments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six assertions in comments this branch added did not survive checking against the code they name: * `add_fn_ptr` was cited at `jitcode/assembler.rs:4617`; this branch inserted 16 lines earlier in that file, moving it to 4633. * The `f64::to_bits`-packing `_concrete` wrapper was attributed to `#[jit_module]` twice. It is emitted by `emit_helper_call_target_fn` (`majit-macros/src/lib.rs:605-614`), reached from the per-helper policy attributes. Record also that `jit_release_gil` reaches it through `_call_aroundstate_target_`, whose first element is that wrapper. * "that i64 wrapper" in the CALL_ASSEMBLER sentence corefered to `_concrete`; that arm wants its own `call_assembler` entry wrapper, a different one. * The NULL-Ref fold refusal claimed upstream's optimizer inserts `guard_nonnull` ahead of a pointer-deref residual call. `GUARD_NONNULL` is emitted from `pyjitpl.py:558-575 _establish_nullity`, i.e. from the traced program's own null test; upstream simply never derives that NULL. * The same comment named `PyFrame.f_back`; the registered field is `PyFrame.f_backref`. It carries `immutable = false`, so `is_always_pure()` is false and the constant-pool arm is unreachable for it — the NULL is stamped onto the recorded OpRef via `set_opref_concrete`. * Two test doc comments stated that reading the integer return register returned the argument still sitting in it. That register is undefined after a call to an `f64`-returning callee. Also narrow the `executor.py:66-68` citation to the result half and cite `descr.py:604-605` for the argument half. Assisted-by: Claude --- majit/majit-metainterp/src/executor.rs | 28 +++++++++++-------- .../majit-metainterp/src/jitcode/assembler.rs | 9 ++++-- .../src/jitcode_dispatch/residual_call.rs | 13 +++++---- .../src/jitcode_dispatch/tests.rs | 4 +-- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/majit/majit-metainterp/src/executor.rs b/majit/majit-metainterp/src/executor.rs index b7d348b5b0b..53b2e9dcdf3 100644 --- a/majit/majit-metainterp/src/executor.rs +++ b/majit/majit-metainterp/src/executor.rs @@ -811,18 +811,20 @@ pub fn execute_pure_call( // executor.py:66-68 `if rettype == FLOAT: cpu.bh_call_f(...)`. The // funcbox reaching this seam is always the callee's own address — // `add_fn_ptr(ptr)` is `add_call_target(ptr, ptr)` - // (`jitcode/assembler.rs:4617`), which is what pyre's codewriter + // (`jitcode/assembler.rs:4633`), which is what pyre's codewriter // registers, and the LLBC path bakes a plain `ConstInt` fnaddr that // mints no `JitCallTarget` at all. So the f64 comes back in the // floating-point return register, exactly as the blackhole // (`bhimpl_residual_call_irf_f`) and the metainterp's own IRF_F // dispatcher (`pyjitpl/dispatch.rs`) already assume for it. // - // The `f64::to_bits`-packing `_concrete` wrapper `#[jit_module]` emits - // for a Float helper reaches a residual call only through the explicit - // `*_float_wrapped` call policies, which no crate declares; the - // CALL_ASSEMBLER arms that genuinely do want that i64 wrapper are a - // separate opcode family and keep `call_int_function`. + // The `f64::to_bits`-packing `_concrete` wrapper the helper policy + // attributes emit for a Float helper (`emit_helper_call_target_fn`, + // `majit-macros/src/lib.rs:605-614`) reaches a residual call only + // through the explicit `*_float_wrapped` call policies, which no crate + // declares. The CALL_ASSEMBLER arms are a separate opcode family and + // keep `call_int_function` for their own i64-returning `call_assembler` + // entry wrapper (`pyjitpl/dispatch.rs`), which is a different wrapper. // // The result is returned as raw bits so the caller's `f64::from_bits` // stamp is unchanged (`longlong` float-storage parity). @@ -1019,10 +1021,11 @@ mod execute_pure_call_tests { assert_eq!(result, 123, "add3_i64(100, 20, 3) must return 123"); } - /// executor.py:66-68 — a Float result is fetched through `cpu.bh_call_f`, - /// i.e. the floating-point return register, and a Float argument travels - /// in the FP argument file. `args` carries the raw `f64::to_bits` for - /// both directions (`longlong` float-storage parity). + /// A Float result is fetched through `cpu.bh_call_f` (`executor.py:66-68`), + /// i.e. the floating-point return register. What puts a Float *argument* + /// in the FP argument file is the stub's `lltype.Float` parameter + /// (`descr.py:604-605 ARGS`/`FUNC`). `args` carries the raw `f64::to_bits` + /// in both directions (`longlong` float-storage parity). #[test] fn float_result_and_float_arg_use_the_floating_point_register_file() { let descr = make_descr(vec![Type::Float], Type::Float); @@ -1035,8 +1038,9 @@ mod execute_pure_call_tests { } /// The mixed shape that motivated the fix: an integer parameter with an - /// `f64` return. Reading the integer return register here yielded the - /// argument still sitting in it rather than the result. + /// `f64` return. The integer return register is undefined after a call to + /// an `f64`-returning callee, so reading it here read residue rather than + /// the result. #[test] fn float_result_with_an_integer_arg_reads_the_float_return_register() { let descr = make_descr(vec![Type::Int], Type::Float); diff --git a/majit/majit-metainterp/src/jitcode/assembler.rs b/majit/majit-metainterp/src/jitcode/assembler.rs index eb9290d46c4..0fbdfdac39e 100644 --- a/majit/majit-metainterp/src/jitcode/assembler.rs +++ b/majit/majit-metainterp/src/jitcode/assembler.rs @@ -3656,9 +3656,12 @@ impl JitCodeBuilder { // arm through an `extern "C" fn(..) -> f64` ABI, i.e. out of the // floating-point return register. That is right for a raw callee // address — `add_fn_ptr(ptr)` is `add_call_target(ptr, ptr)` — and - // wrong for a `_concrete` wrapper, which `#[jit_module]` gives an - // `-> i64` signature carrying `f64::to_bits`. Only the macro's - // `*_float_wrapped` call policies mint that divergence, and no crate + // wrong for a `_concrete` wrapper, which the helper policy attributes + // give an `-> i64` signature carrying `f64::to_bits` + // (`emit_helper_call_target_fn`, `majit-macros/src/lib.rs:605-614`). + // Only the `*_float_wrapped` call policies mint that divergence — for + // `jit_release_gil` it arrives via `_call_aroundstate_target_`, + // whose first element is that same `_concrete` wrapper — and no crate // declares one; assert the invariant here so the first declaration // trips a build rather than silently stamping a float read out of the // integer return register. diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs index 098375fb2bb..60da01196f6 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/residual_call.rs @@ -1651,12 +1651,15 @@ pub(crate) fn try_fold_pure_call_via_executor( // // Pyre's walker folds from a different source of constants. Its // getfield_gc_r handler propagates field reads (including pointer-valued - // fields like `PyFrame.f_back`) as concrete values whenever the parent + // fields like `PyFrame.f_backref`) as concrete values whenever the parent // struct is concrete-known, so a top-level frame stamps - // `Value::Ref(GcRef(0))` into the constant pool where upstream would still - // hold a symbolic box guarded by the `guard_nonnull` its optimizer inserts - // ahead of a pointer-deref residual call. Executing `helper(NULL)` here - // would dereference NULL and SEGV before that guard exists. + // `Value::Ref(GcRef(0))` onto the recorded `GetfieldGcR` OpRef + // (`set_opref_concrete`), which `box_value` then hands straight to this + // fold. Upstream never derives that NULL in the first place: a pointer + // becomes nonnull-known there only from the traced program's own null test + // (`pyjitpl.py:558-575 _establish_nullity`), so the box stays symbolic and + // the call is never folded on a NULL it invented. Executing + // `helper(NULL)` here would dereference NULL and SEGV. // // So guard the executor entry against NULL Ref arguments and fall through // to recording the IR op as-is. The downstream optimizer then sees the diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs index 28bb52ac7e6..e5df775007e 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/tests.rs @@ -11410,8 +11410,8 @@ fn ref_compare_same_box_fastpath_covers_the_instance_ptr_spellings() { // from the floating-point return register (`executor.py:66-68 cpu.bh_call_f`). /// A real `f64`-returning callee with an integer parameter, i.e. the -/// `jit_bigint_to_f64_or_inf` shape. Reading the integer return register -/// here returned the argument still sitting in it. +/// `jit_bigint_to_f64_or_inf` shape. The integer return register is undefined +/// after such a call, so reading it here read residue, not the result. extern "C" fn halve_f64_for_walker_test(x: i64) -> f64 { (x as f64) / 2.0 } From 19027ff1935cd04aa9e7f7803ddd1011e39830a4 Mon Sep 17 00:00:00 2001 From: Jeong YunWon Date: Mon, 3 Aug 2026 14:38:59 +0900 Subject: [PATCH 4/4] jit: give the Int/Ref and Void residual-call arms their argument classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `executor.py:52-78` hands `descr` to `cpu.bh_call_i`, `cpu.bh_call_r` and `cpu.bh_call_v` exactly as it does to `cpu.bh_call_f`, so the generated stub's parameter types come from `arg_classes` on every path — a Float parameter is class `'f'` whatever the result type is. pyre routed only the Float-result arm that way. The Int/Ref arm called `call_int_function`, which takes no `arg_types` and passes every argument as `i64`. The Void arm matched one hardcoded shape, `[Ref, Int, Int, Float]`, and fell back to the same all-`i64` path for every other float-carrying signature. Both handed a Float parameter to the callee in an integer register. Extract `arg_classes_from_types` from `call_float_function`, add `call_int_function_typed`, and rewrite `call_void_function_typed` on top of it; point `execute_pure_call` and `execute_residual_call` at both. `call_int_function` stays for the seams that hold no descr — `execute_varargs`'s portal runner and the CALL_ASSEMBLER family, whose entry wrapper is `extern "C" fn(..) -> i64` by construction. Both new tests were confirmed to fail against the previous arms. A callee taking a single `f64` cannot make that check: the value it wants may be left in xmm0 by the caller, so the first draft passed against the broken path. Both tests therefore take `(f64, i64)` and encode both arguments in the result. Assisted-by: Claude --- majit/majit-metainterp/src/executor.rs | 65 ++++++++++++- majit/majit-metainterp/src/pyjitpl.rs | 4 +- .../majit-metainterp/src/pyjitpl/dispatch.rs | 92 ++++++++++++++----- 3 files changed, 132 insertions(+), 29 deletions(-) diff --git a/majit/majit-metainterp/src/executor.rs b/majit/majit-metainterp/src/executor.rs index 53b2e9dcdf3..5037ae1894f 100644 --- a/majit/majit-metainterp/src/executor.rs +++ b/majit/majit-metainterp/src/executor.rs @@ -798,11 +798,13 @@ pub fn execute_pure_call( ); let func_ptr = func_ptr as *const (); match descr.result_type() { - // RPython dispatches Int and Ref through the same backend primitive - // `cpu.bh_call_i` (returns i64); pyre's `call_int_function` does - // the same — Ref is bit-identical to Int at the ABI level. + // executor.py:52-65 dispatches Int through `cpu.bh_call_i` and Ref + // through `cpu.bh_call_r`; both return a machine word, so pyre shares + // one dispatcher — Ref is bit-identical to Int at the ABI level. The + // descr goes along for the *arguments*: a Float parameter belongs in + // the floating-point register file whatever the result type is. majit_ir::Type::Int | majit_ir::Type::Ref => { - crate::pyjitpl::call_int_function(func_ptr, args) + crate::pyjitpl::call_int_function_typed(func_ptr, args, descr.arg_types()) } majit_ir::Type::Void => { crate::pyjitpl::call_void_function_typed(func_ptr, args, descr.arg_types()); @@ -875,7 +877,7 @@ pub fn execute_residual_call( let func_ptr = func_ptr as *const (); let result = match descr.result_type() { majit_ir::Type::Int | majit_ir::Type::Ref => { - crate::pyjitpl::call_int_function(func_ptr, args) + crate::pyjitpl::call_int_function_typed(func_ptr, args, descr.arg_types()) } majit_ir::Type::Void => { crate::pyjitpl::call_void_function_typed(func_ptr, args, descr.arg_types()); @@ -1001,6 +1003,23 @@ mod execute_pure_call_tests { extern "C" fn void_no_op(_x: i64) {} + /// An Int result with a Float parameter — the direction the Float-result + /// work did not cover. The return encodes BOTH arguments: a callee taking + /// a single `f64` cannot tell a correct call from a wrong-register one, + /// because the value it wants may happen to be left in xmm0 by the caller. + extern "C" fn float_then_int_to_i64(x: f64, n: i64) -> i64 { + x as i64 * 100 + n + } + + /// The interleaved void shape. Records both arguments so the test can tell + /// a right-register call from one that shuffled them. + static VOID_INTERLEAVED_SEEN: std::sync::atomic::AtomicI64 = + std::sync::atomic::AtomicI64::new(0); + + extern "C" fn void_float_then_int(x: f64, n: i64) { + VOID_INTERLEAVED_SEEN.store(x as i64 * 100 + n, std::sync::atomic::Ordering::SeqCst); + } + fn make_descr(arg_types: Vec, result_type: Type) -> SimpleCallDescr { let mut effect = EffectInfo::default(); effect.extraeffect = ExtraEffect::ElidableCannotRaise; @@ -1021,6 +1040,42 @@ mod execute_pure_call_tests { assert_eq!(result, 123, "add3_i64(100, 20, 3) must return 123"); } + /// `executor.py:52-58` hands `descr` to `cpu.bh_call_i` just as `:66-72` + /// does to `cpu.bh_call_f`, so a Float parameter is class-`'f'` whatever + /// the result type is. + #[test] + fn int_result_reads_a_float_argument_from_the_float_register_file() { + let descr = make_descr(vec![Type::Float, Type::Int], Type::Int); + let result = execute_pure_call( + &descr, + float_then_int_to_i64 as *const () as i64, + &[7.0_f64.to_bits() as i64, 3], + ); + assert_eq!( + result, 703, + "float_then_int_to_i64(7.0, 3) == 703; an all-i64 pass hands the \ + callee the raw bits as its integer argument" + ); + } + + /// `executor.py:73-77 cpu.bh_call_v` takes the same `descr`, so a void + /// callee's Float parameter is placed the same way. + #[test] + fn void_result_places_an_interleaved_float_argument_in_order() { + VOID_INTERLEAVED_SEEN.store(0, std::sync::atomic::Ordering::SeqCst); + let descr = make_descr(vec![Type::Float, Type::Int], Type::Void); + execute_pure_call( + &descr, + void_float_then_int as *const () as i64, + &[7.0_f64.to_bits() as i64, 3], + ); + assert_eq!( + VOID_INTERLEAVED_SEEN.load(std::sync::atomic::Ordering::SeqCst), + 703, + "void_float_then_int(7.0, 3) must see 7.0 and 3 in declaration order" + ); + } + /// A Float result is fetched through `cpu.bh_call_f` (`executor.py:66-68`), /// i.e. the floating-point return register. What puts a Float *argument* /// in the FP argument file is the stub's `lltype.Float` parameter diff --git a/majit/majit-metainterp/src/pyjitpl.rs b/majit/majit-metainterp/src/pyjitpl.rs index a7ec5b3f327..449a5a0183c 100644 --- a/majit/majit-metainterp/src/pyjitpl.rs +++ b/majit/majit-metainterp/src/pyjitpl.rs @@ -9,8 +9,8 @@ pub use dispatch::{ }; pub use dispatch::{build_vable_snapshot_boxes, build_vref_snapshot_boxes}; pub use dispatch::{ - call_float_function, call_int_function, call_ref_function, call_void_function, - call_void_function_typed, + call_float_function, call_int_function, call_int_function_typed, call_ref_function, + call_void_function, call_void_function_typed, }; pub use dispatch::{eval_binop_f, eval_binop_i, eval_float_cmp, eval_unary_f, eval_unary_i}; pub use frame::{MIFrame, MIFrameStack}; diff --git a/majit/majit-metainterp/src/pyjitpl/dispatch.rs b/majit/majit-metainterp/src/pyjitpl/dispatch.rs index a6505615759..14ab12db39d 100644 --- a/majit/majit-metainterp/src/pyjitpl/dispatch.rs +++ b/majit/majit-metainterp/src/pyjitpl/dispatch.rs @@ -8461,22 +8461,69 @@ pub fn call_float_function(func_ptr: *const (), args: &[i64], arg_types: &[Type] if let Some(hook) = majit_backend::call_stub::residual_host_call() { return f64::from_bits(hook(func_ptr as usize, args) as u64); } + let classes = arg_classes_from_types(args.len(), arg_types); + unsafe { + majit_backend::call_stub::bh_call_f_dispatch( + func_ptr as usize, + &classes[..args.len()], + args, + ) + } +} + +/// `descr.arg_types()` projected onto the ordered C-ABI class list the +/// `bh_call_*_dispatch` table takes. +/// +/// This is `descr.py:648-649`'s `arg_classes = map(map_type_to_argclass, ARGS)` +/// collapsed onto the two register classes the table can express: `Int` and +/// `Ref` both travel in an integer register, `Float` in a floating-point one +/// (`descr.py:556-570 TYPE()`). +/// +/// Returns the fixed-size buffer so the residual-call path stays +/// allocation-free; callers slice it to `args.len()`. A positional slot with +/// no declared type is forwarded as a machine word, matching the untyped +/// [`call_int_function`] / [`call_void_function`] seams. +fn arg_classes_from_types( + args_len: usize, + arg_types: &[Type], +) -> [majit_backend::call_stub::ArgClass; MAX_HOST_CALL_ARITY] { assert!( - args.len() <= MAX_HOST_CALL_ARITY, - "unsupported JitCode float call arity {} (max {})", - args.len(), - MAX_HOST_CALL_ARITY + args_len <= MAX_HOST_CALL_ARITY, + "unsupported JitCode typed call arity {args_len} (max {MAX_HOST_CALL_ARITY})" ); let mut classes = [majit_backend::call_stub::ArgClass::Int; MAX_HOST_CALL_ARITY]; - for i in 0..args.len() { - classes[i] = match arg_types.get(i) { + for (i, slot) in classes.iter_mut().enumerate().take(args_len) { + *slot = match arg_types.get(i) { Some(Type::Float) => majit_backend::call_stub::ArgClass::Float, Some(Type::Int | Type::Ref) | None => majit_backend::call_stub::ArgClass::Int, - Some(Type::Void) => panic!("call_float_function: void argument at slot {i}"), + // `descr.py:566-567 TYPE('v')` is `lltype.Void`, which upstream + // never puts in `arg_classes` for a call it dispatches. + Some(Type::Void) => panic!("typed call: void argument class at slot {i}"), }; } + classes +} + +/// `bh_call_i` / `bh_call_r` parity (`llmodel.py:816`, `:818`) for callers that +/// hold the `CallDescr`. +/// +/// `executor.py:52-65` hands `descr` to `cpu.bh_call_i` and `cpu.bh_call_r` +/// exactly as `:66-72` does to `cpu.bh_call_f`, so the generated stub's +/// parameter types come from `arg_classes` on all three paths. An Int- or +/// Ref-returning callee with a `Float` parameter therefore reads it from the +/// floating-point register file, which [`call_int_function`]'s all-`i64` +/// transmute cannot deliver. +/// +/// [`call_int_function`] stays for the seams that genuinely hold no descr — +/// `execute_varargs`'s portal runner and the CALL_ASSEMBLER family, whose +/// entry wrapper is `extern "C" fn(..) -> i64` by construction. +pub fn call_int_function_typed(func_ptr: *const (), args: &[i64], arg_types: &[Type]) -> i64 { + if let Some(hook) = majit_backend::call_stub::residual_host_call() { + return hook(func_ptr as usize, args); + } + let classes = arg_classes_from_types(args.len(), arg_types); unsafe { - majit_backend::call_stub::bh_call_f_dispatch( + majit_backend::call_stub::bh_call_i_dispatch( func_ptr as usize, &classes[..args.len()], args, @@ -8710,24 +8757,25 @@ pub fn call_void_function(func_ptr: *const (), args: &[i64]) { } } -/// Void-call dispatcher for signatures carrying Float-bank arguments. Args -/// reach here packed into machine words, so a float argument holds its bits in -/// an `i64` while the callee's C ABI expects it in a floating-point register; -/// only `arg_types` still records which is which. Shapes with no float -/// argument, and the host-trampoline path (which reflects the callee's real -/// signature and coerces each argument itself), defer to `call_void_function`. +/// `bh_call_v` parity (`llmodel.py:834`) for callers that hold the `CallDescr`. +/// +/// Args reach here packed into machine words, so a float argument holds its +/// bits in an `i64` while the callee's C ABI expects it in a floating-point +/// register; only `arg_types` still records which is which. The +/// host-trampoline path reflects the callee's real signature and coerces each +/// argument itself, so it takes the positional list unchanged. pub fn call_void_function_typed(func_ptr: *const (), args: &[i64], arg_types: &[Type]) { - if majit_backend::call_stub::residual_host_call().is_some() || !arg_types.contains(&Type::Float) - { + if majit_backend::call_stub::residual_host_call().is_some() { call_void_function(func_ptr, args); return; } - match (args, arg_types) { - ([a0, a1, a2, a3], [Type::Ref, Type::Int, Type::Int, Type::Float]) => unsafe { - let func: extern "C" fn(i64, i64, i64, f64) = std::mem::transmute(func_ptr); - func(*a0, *a1, *a2, f64::from_bits(*a3 as u64)); - }, - _ => call_void_function(func_ptr, args), + let classes = arg_classes_from_types(args.len(), arg_types); + unsafe { + majit_backend::call_stub::bh_call_v_dispatch( + func_ptr as usize, + &classes[..args.len()], + args, + ); } }