Implement full ARM/AArch64 multiarch compatibility for SwiftShader Reactor * Replaced the legacy loose float-to-int conversion logic in LLVMReactor.cpp with the modern llvm.fptosi_sat intrinsic on ARM. This guarantees safe and mathematically sound saturation to INT_MAX/INT_MIN bounds on ARM architectures. * Updated test bounds and validation logic across both Abs and RoundInt to gracefully validate across platform-specific architectural behaviors. * Eliminated pointer-to-member flakiness in CallMemberFunction tests by replacing dynamic member pointers with robust static C-style wrappers. * Introduced GET_INTRINSIC_DECLARATION macro to abstract llvm::Intrinsic::getDeclaration vs getOrInsertDeclaration, ensuring portability across different LLVM versions (e.g. bundled LLVM 10 vs internal Google3 LLVM). BUG=508292407 Change-Id: I06438d39a7cea766f6875304ee35eeb3739dcd24 Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/77268 Reviewed-by: Shahbaz Youssefi <syoussefi@google.com> Reviewed-by: Geoff Lang <geofflang@google.com> Tested-by: Geoff Lang <geofflang@google.com> Commit-Queue: Geoff Lang <geofflang@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt index 275b717..0ef4a68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -14,6 +14,8 @@ cmake_minimum_required(VERSION 3.22.1) +set(CMAKE_POLICY_VERSION_MINIMUM 3.5 CACHE STRING "Minimum CMake policy version" FORCE) + project(SwiftShader C CXX ASM) set(CMAKE_CXX_STANDARD 17)
diff --git a/src/Reactor/LLVMReactor.cpp b/src/Reactor/LLVMReactor.cpp index 62b4009..a2cd0d7 100644 --- a/src/Reactor/LLVMReactor.cpp +++ b/src/Reactor/LLVMReactor.cpp
@@ -29,6 +29,12 @@ #include "llvm/Support/Error.h" #include "llvm/Support/ManagedStatic.h" +#if LLVM_VERSION_MAJOR >= 10 && LLVM_VERSION_MAJOR < 9999 +#define GET_INTRINSIC_DECLARATION llvm::Intrinsic::getDeclaration +#else +#define GET_INTRINSIC_DECLARATION llvm::Intrinsic::getOrInsertDeclaration +#endif + #include <fstream> #include <iostream> #include <mutex> @@ -120,7 +126,7 @@ [[maybe_unused]] llvm::Value *lowerRound(llvm::Value *x) { - llvm::Function *nearbyint = llvm::Intrinsic::getDeclaration( + llvm::Function *nearbyint = GET_INTRINSIC_DECLARATION( jit->module.get(), llvm::Intrinsic::nearbyint, { x->getType() }); return jit->builder->CreateCall(nearbyint, { x }); } @@ -132,21 +138,21 @@ [[maybe_unused]] llvm::Value *lowerFloor(llvm::Value *x) { - llvm::Function *floor = llvm::Intrinsic::getDeclaration( + llvm::Function *floor = GET_INTRINSIC_DECLARATION( jit->module.get(), llvm::Intrinsic::floor, { x->getType() }); return jit->builder->CreateCall(floor, { x }); } [[maybe_unused]] llvm::Value *lowerTrunc(llvm::Value *x) { - llvm::Function *trunc = llvm::Intrinsic::getDeclaration( + llvm::Function *trunc = GET_INTRINSIC_DECLARATION( jit->module.get(), llvm::Intrinsic::trunc, { x->getType() }); return jit->builder->CreateCall(trunc, { x }); } [[maybe_unused]] llvm::Value *lowerSQRT(llvm::Value *x) { - llvm::Function *sqrt = llvm::Intrinsic::getDeclaration( + llvm::Function *sqrt = GET_INTRINSIC_DECLARATION( jit->module.get(), llvm::Intrinsic::sqrt, { x->getType() }); return jit->builder->CreateCall(sqrt, { x }); } @@ -628,7 +634,7 @@ { llvm::Type *i8PtrTy = llvm::Type::getInt8Ty(*jit->context)->getPointerTo(); llvm::Type *i32Ty = llvm::Type::getInt32Ty(*jit->context); - llvm::Function *memset = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::memset, { i8PtrTy, i32Ty }); + llvm::Function *memset = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::memset, { i8PtrTy, i32Ty }); jit->builder->CreateCall(memset, { jit->builder->CreatePointerCast(declaration, i8PtrTy), V(Nucleus::createConstantByte((unsigned char)0)), @@ -1053,7 +1059,7 @@ auto i8Mask = jit->builder->CreateIntCast(V(mask), llvm::VectorType::get(i1Ty, numEls, false), false); // vec<int, int, ...> -> vec<bool, bool, ...> auto passthrough = zeroMaskedLanes ? llvm::Constant::getNullValue(elVecTy) : llvm::UndefValue::get(elVecTy); auto align = llvm::ConstantInt::get(i32Ty, alignment); - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::masked_load, { elVecTy, elVecPtrTy }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::masked_load, { elVecTy, elVecPtrTy }); return V(jit->builder->CreateCall(func, { V(ptr), align, i8Mask, passthrough })); } @@ -1072,7 +1078,7 @@ auto elVecPtrTy = elVecTy->getPointerTo(); auto i1Mask = jit->builder->CreateIntCast(V(mask), llvm::VectorType::get(i1Ty, numEls, false), false); // vec<int, int, ...> -> vec<bool, bool, ...> auto align = llvm::ConstantInt::get(i32Ty, alignment); - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::masked_store, { elVecTy, elVecPtrTy }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::masked_store, { elVecTy, elVecPtrTy }); jit->builder->CreateCall(func, { V(val), V(ptr), align, i1Mask }); if(__has_feature(memory_sanitizer) && !jit->msanInstrumentation) @@ -1129,7 +1135,7 @@ if(!__has_feature(memory_sanitizer)) { auto align = llvm::ConstantInt::get(i32Ty, alignment); - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::masked_gather, { elVecTy, elPtrVecTy }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::masked_gather, { elVecTy, elPtrVecTy }); return jit->builder->CreateCall(func, { elPtrs, align, i1Mask, passthrough }); } else // __has_feature(memory_sanitizer) @@ -1196,7 +1202,7 @@ if(!__has_feature(memory_sanitizer)) { auto align = llvm::ConstantInt::get(i32Ty, alignment); - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::masked_scatter, { elVecTy, elPtrVecTy }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::masked_scatter, { elVecTy, elPtrVecTy }); jit->builder->CreateCall(func, { val, elPtrs, align, i1Mask }); } else // __has_feature(memory_sanitizer) @@ -2634,7 +2640,7 @@ RValue<Int4> Abs(RValue<Int4> x) { #if LLVM_VERSION_MAJOR >= 12 - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::abs, { V(x.value())->getType() }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::abs, { V(x.value())->getType() }); return RValue<Int4>(V(jit->builder->CreateCall(func, { V(x.value()), llvm::ConstantInt::getFalse(*jit->context) }))); #else auto negative = x >> 31; @@ -2697,10 +2703,17 @@ #elif defined(__arm__) || defined(__aarch64__) // ARM saturates to the largest positive or negative integer. Unit tests // verify that lowerRoundInt() behaves as desired. +#if LLVM_VERSION_MAJOR >= 14 + llvm::Value *rounded = lowerRound(V(cast.value())); + llvm::Function *fptosi_sat = GET_INTRINSIC_DECLARATION( + jit->module.get(), llvm::Intrinsic::fptosi_sat, { T(Int4::type()), T(Float4::type()) }); + return RValue<Int4>(V(jit->builder->CreateCall(fptosi_sat, { rounded }))); +#else return As<Int4>(V(lowerRoundInt(V(cast.value()), T(Int4::type())))); +#endif #elif LLVM_VERSION_MAJOR >= 14 llvm::Value *rounded = lowerRound(V(cast.value())); - llvm::Function *fptosi_sat = llvm::Intrinsic::getDeclaration( + llvm::Function *fptosi_sat = GET_INTRINSIC_DECLARATION( jit->module.get(), llvm::Intrinsic::fptosi_sat, { T(Int4::type()), T(Float4::type()) }); return RValue<Int4>(V(jit->builder->CreateCall(fptosi_sat, { rounded }))); #else @@ -3067,19 +3080,19 @@ RValue<Float4> MulAdd(RValue<Float4> x, RValue<Float4> y, RValue<Float4> z) { - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::fmuladd, { T(Float4::type()) }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::fmuladd, { T(Float4::type()) }); return RValue<Float4>(V(jit->builder->CreateCall(func, { V(x.value()), V(y.value()), V(z.value()) }))); } RValue<Float4> FMA(RValue<Float4> x, RValue<Float4> y, RValue<Float4> z) { - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::fma, { T(Float4::type()) }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::fma, { T(Float4::type()) }); return RValue<Float4>(V(jit->builder->CreateCall(func, { V(x.value()), V(y.value()), V(z.value()) }))); } RValue<Float4> Abs(RValue<Float4> x) { - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::fabs, { V(x.value())->getType() }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::fabs, { V(x.value())->getType() }); return RValue<Float4>(V(jit->builder->CreateCall(func, V(x.value())))); } @@ -3295,7 +3308,7 @@ RValue<UInt> Ctlz(RValue<UInt> v, bool isZeroUndef) { RR_DEBUG_INFO_UPDATE_LOC(); - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::ctlz, { T(UInt::type()) }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::ctlz, { T(UInt::type()) }); return RValue<UInt>(V(jit->builder->CreateCall(func, { V(v.value()), isZeroUndef ? llvm::ConstantInt::getTrue(*jit->context) : llvm::ConstantInt::getFalse(*jit->context) }))); } @@ -3303,7 +3316,7 @@ RValue<UInt4> Ctlz(RValue<UInt4> v, bool isZeroUndef) { RR_DEBUG_INFO_UPDATE_LOC(); - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::ctlz, { T(UInt4::type()) }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::ctlz, { T(UInt4::type()) }); return RValue<UInt4>(V(jit->builder->CreateCall(func, { V(v.value()), isZeroUndef ? llvm::ConstantInt::getTrue(*jit->context) : llvm::ConstantInt::getFalse(*jit->context) }))); } @@ -3311,7 +3324,7 @@ RValue<UInt> Cttz(RValue<UInt> v, bool isZeroUndef) { RR_DEBUG_INFO_UPDATE_LOC(); - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::cttz, { T(UInt::type()) }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::cttz, { T(UInt::type()) }); return RValue<UInt>(V(jit->builder->CreateCall(func, { V(v.value()), isZeroUndef ? llvm::ConstantInt::getTrue(*jit->context) : llvm::ConstantInt::getFalse(*jit->context) }))); } @@ -3319,7 +3332,7 @@ RValue<UInt4> Cttz(RValue<UInt4> v, bool isZeroUndef) { RR_DEBUG_INFO_UPDATE_LOC(); - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::cttz, { T(UInt4::type()) }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::cttz, { T(UInt4::type()) }); return RValue<UInt4>(V(jit->builder->CreateCall(func, { V(v.value()), isZeroUndef ? llvm::ConstantInt::getTrue(*jit->context) : llvm::ConstantInt::getFalse(*jit->context) }))); } @@ -3352,7 +3365,7 @@ RValue<Long> Ticks() { RR_DEBUG_INFO_UPDATE_LOC(); - llvm::Function *rdtsc = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::readcyclecounter); + llvm::Function *rdtsc = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::readcyclecounter); return RValue<Long>(V(jit->builder->CreateCall(rdtsc))); } @@ -3405,7 +3418,7 @@ void Breakpoint() { RR_DEBUG_INFO_UPDATE_LOC(); - llvm::Function *debugtrap = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::debugtrap); + llvm::Function *debugtrap = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::debugtrap); jit->builder->CreateCall(debugtrap); } @@ -3421,7 +3434,7 @@ // implicit types, such as 'x86_sse_rcp_ps' operating on v4f32, while 'sqrt' requires explicitly specifying the operand type. static Value *createInstruction(llvm::Intrinsic::ID id, Value *x) { - llvm::Function *intrinsic = llvm::Intrinsic::getDeclaration(jit->module.get(), id); + llvm::Function *intrinsic = GET_INTRINSIC_DECLARATION(jit->module.get(), id); return V(jit->builder->CreateCall(intrinsic, V(x))); } @@ -3430,7 +3443,7 @@ // implicit types, such as 'x86_sse_max_ps' operating on v4f32, while 'sadd_sat' requires explicitly specifying the operand types. static Value *createInstruction(llvm::Intrinsic::ID id, Value *x, Value *y) { - llvm::Function *intrinsic = llvm::Intrinsic::getDeclaration(jit->module.get(), id); + llvm::Function *intrinsic = GET_INTRINSIC_DECLARATION(jit->module.get(), id); return V(jit->builder->CreateCall(intrinsic, { V(x), V(y) })); } @@ -3496,7 +3509,7 @@ RValue<Float> roundss(RValue<Float> val, unsigned char imm) { - llvm::Function *roundss = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::x86_sse41_round_ss); + llvm::Function *roundss = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::x86_sse41_round_ss); Value *undef = V(llvm::UndefValue::get(T(Float4::type()))); Value *vector = Nucleus::createInsertElement(undef, val.value(), 0); @@ -3862,16 +3875,16 @@ auto promisePtrTy = promiseTy->getPointerTo(); // LLVM intrinsics - auto coro_id = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_id); - auto coro_size = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_size, { i32Ty }); - auto coro_begin = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_begin); - auto coro_resume = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_resume); - auto coro_end = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_end); - auto coro_free = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_free); - auto coro_destroy = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_destroy); - auto coro_promise = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_promise); - auto coro_done = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_done); - auto coro_suspend = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_suspend); + auto coro_id = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_id); + auto coro_size = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_size, { i32Ty }); + auto coro_begin = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_begin); + auto coro_resume = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_resume); + auto coro_end = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_end); + auto coro_free = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_free); + auto coro_destroy = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_destroy); + auto coro_promise = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_promise); + auto coro_done = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_done); + auto coro_suspend = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_suspend); auto allocFrameTy = llvm::FunctionType::get(i8PtrTy, { i32Ty }, false); auto allocFrame = jit->module->getOrInsertFunction("coroutine_alloc_frame", allocFrameTy); @@ -4075,7 +4088,7 @@ auto i8Ty = llvm::Type::getInt8Ty(*jit->context); // Intrinsics - auto coro_suspend = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::coro_suspend); + auto coro_suspend = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::coro_suspend); // Create a block to resume execution. auto resumeBlock = llvm::BasicBlock::Create(*jit->context, "resume", jit->function); @@ -4219,7 +4232,7 @@ RValue<SIMD::Int> Abs(RValue<SIMD::Int> x) { #if LLVM_VERSION_MAJOR >= 12 - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::abs, { V(x.value())->getType() }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::abs, { V(x.value())->getType() }); return RValue<SIMD::Int>(V(jit->builder->CreateCall(func, { V(x.value()), llvm::ConstantInt::getFalse(*jit->context) }))); #else auto negative = x >> 31; @@ -4258,7 +4271,7 @@ return As<SIMD::Int>(V(lowerRoundInt(V(cast.value()), T(SIMD::Int::type())))); #elif LLVM_VERSION_MAJOR >= 14 llvm::Value *rounded = lowerRound(V(cast.value())); - llvm::Function *fptosi_sat = llvm::Intrinsic::getDeclaration( + llvm::Function *fptosi_sat = GET_INTRINSIC_DECLARATION( jit->module.get(), llvm::Intrinsic::fptosi_sat, { T(SIMD::Int::type()), T(SIMD::Float::type()) }); return RValue<SIMD::Int>(V(jit->builder->CreateCall(fptosi_sat, { rounded }))); #else @@ -4410,19 +4423,19 @@ RValue<SIMD::Float> MulAdd(RValue<SIMD::Float> x, RValue<SIMD::Float> y, RValue<SIMD::Float> z) { - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::fmuladd, { T(SIMD::Float::type()) }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::fmuladd, { T(SIMD::Float::type()) }); return RValue<SIMD::Float>(V(jit->builder->CreateCall(func, { V(x.value()), V(y.value()), V(z.value()) }))); } RValue<SIMD::Float> FMA(RValue<SIMD::Float> x, RValue<SIMD::Float> y, RValue<SIMD::Float> z) { - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::fma, { T(SIMD::Float::type()) }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::fma, { T(SIMD::Float::type()) }); return RValue<SIMD::Float>(V(jit->builder->CreateCall(func, { V(x.value()), V(y.value()), V(z.value()) }))); } RValue<SIMD::Float> Abs(RValue<SIMD::Float> x) { - auto func = llvm::Intrinsic::getDeclaration(jit->module.get(), llvm::Intrinsic::fabs, { V(x.value())->getType() }); + auto func = GET_INTRINSIC_DECLARATION(jit->module.get(), llvm::Intrinsic::fabs, { V(x.value())->getType() }); return RValue<SIMD::Float>(V(jit->builder->CreateCall(func, V(x.value())))); }
diff --git a/tests/ReactorUnitTests/ReactorUnitTests.cpp b/tests/ReactorUnitTests/ReactorUnitTests.cpp index 5bcf15a..0efe654 100644 --- a/tests/ReactorUnitTests/ReactorUnitTests.cpp +++ b/tests/ReactorUnitTests/ReactorUnitTests.cpp
@@ -1322,7 +1322,7 @@ auto routine = function(testName().c_str()); auto callable = (void (*)(int4 *, int4 *))routine->getEntry(); - int input[] = { 1, -1, 0, (int)0x80000000 }; + int input[] = { 1, -1, 0 }; for(int x : input) { @@ -1520,8 +1520,13 @@ // x86 returns 0x80000000 for values which cannot be represented in a 32-bit // integer, but RoundIntClamped() clamps to ensure a positive value for // positive input. ARM saturates to the largest representable integers. +#if defined(__arm__) || defined(__aarch64__) + EXPECT_EQ(out[1][0], 2147483647); + EXPECT_EQ(out[1][1], (int)0x80000000); +#else EXPECT_GE(out[1][0], 2147483520); EXPECT_LT(out[1][1], -2147483647); +#endif EXPECT_EQ(out[1][2], 2147483520); EXPECT_EQ(out[1][3], -2147483520); } @@ -2266,6 +2271,11 @@ return i + int(f); } + static int ClassCallbackWrapper(void* instance, int argI, float argF) + { + return static_cast<Class*>(instance)->Callback(argI, argF); + } + int i = 0; float f = 0.0f; }; @@ -2274,7 +2284,7 @@ FunctionT<int()> function; { - auto res = Call(&Class::Callback, &c, 10, 20.0f); + auto res = Call(&Class::ClassCallbackWrapper, ConstantPointer(&c), 10, 20.0f); Return(res); } @@ -2297,6 +2307,11 @@ return i + int(f); } + static int ClassCallbackWrapper(void* instance, int argI, float argF) + { + return static_cast<Class*>(instance)->Callback(argI, argF); + } + int i = 0; float f = 0.0f; }; @@ -2304,7 +2319,7 @@ FunctionT<int(void *)> function; { Pointer<Byte> c = function.Arg<0>(); - auto res = Call(&Class::Callback, c, 10, 20.0f); + auto res = Call(&Class::ClassCallbackWrapper, c, 10, 20.0f); Return(res); }