From 225a30163700d03111079cf63e2c1cda455f8d9b Mon Sep 17 00:00:00 2001 From: Samir Jindel Date: Fri, 24 May 2019 02:39:13 +0000 Subject: [PATCH] [vm/ffi] Re-land "[vm/ffi] FFI callbacks on X64." There are minor fixes for dartkb and windows. The original revision is in patchset 1. Change-Id: I9ab6e5fdb33fb4e84ea520c283fe94323616a8ce Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/103129 Commit-Queue: Samir Jindel Auto-Submit: Samir Jindel Reviewed-by: Daco Harkes --- pkg/vm/lib/transformations/ffi_use_sites.dart | 7 +- runtime/bin/ffi_test_functions.cc | 219 ++++++++++++++++- runtime/lib/ffi.cc | 98 +++++--- runtime/lib/ffi_patch.dart | 18 ++ .../vm/compiler/assembler/assembler_ia32.cc | 3 +- .../vm/compiler/assembler/assembler_ia32.h | 1 + .../vm/compiler/assembler/assembler_x64.cc | 17 +- runtime/vm/compiler/assembler/assembler_x64.h | 10 +- .../compiler/backend/constant_propagator.cc | 12 + .../compiler/backend/flow_graph_compiler.cc | 2 +- .../vm/compiler/backend/flow_graph_compiler.h | 5 + runtime/vm/compiler/backend/il.cc | 72 +++++- runtime/vm/compiler/backend/il.h | 146 +++++++++++- runtime/vm/compiler/backend/il_ia32.cc | 2 +- runtime/vm/compiler/backend/il_printer.cc | 22 +- runtime/vm/compiler/backend/il_x64.cc | 171 ++++++++++++- runtime/vm/compiler/backend/linearscan.cc | 25 +- runtime/vm/compiler/ffi.cc | 83 ++++++- runtime/vm/compiler/ffi.h | 25 ++ runtime/vm/compiler/frontend/kernel_to_il.cc | 224 ++++++++++++++---- runtime/vm/compiler/frontend/kernel_to_il.h | 22 ++ runtime/vm/compiler/frontend/scope_builder.cc | 11 + runtime/vm/compiler/runtime_api.cc | 5 + runtime/vm/compiler/runtime_api.h | 3 + runtime/vm/compiler/stub_code_compiler_arm.cc | 4 + .../vm/compiler/stub_code_compiler_arm64.cc | 4 + .../vm/compiler/stub_code_compiler_ia32.cc | 4 + runtime/vm/compiler/stub_code_compiler_x64.cc | 29 ++- runtime/vm/exceptions.cc | 6 + runtime/vm/exceptions.h | 1 + runtime/vm/object.cc | 43 +++- runtime/vm/object.h | 30 ++- runtime/vm/raw_object.h | 14 +- runtime/vm/runtime_entry.cc | 26 ++ runtime/vm/runtime_entry.h | 3 + runtime/vm/runtime_entry_list.h | 3 +- runtime/vm/stack_frame.h | 7 + runtime/vm/stack_frame_x64.h | 9 + runtime/vm/stub_code_list.h | 3 +- runtime/vm/symbols.h | 1 + runtime/vm/thread.cc | 44 ++++ runtime/vm/thread.h | 16 +- tests/ffi/ffi.status | 3 + tests/ffi/function_callbacks_test.dart | 217 +++++++++++++---- 44 files changed, 1475 insertions(+), 195 deletions(-) diff --git a/pkg/vm/lib/transformations/ffi_use_sites.dart b/pkg/vm/lib/transformations/ffi_use_sites.dart index 4b40cafa699..d61ca95e18f 100644 --- a/pkg/vm/lib/transformations/ffi_use_sites.dart +++ b/pkg/vm/lib/transformations/ffi_use_sites.dart @@ -275,9 +275,10 @@ class _FfiUseSiteTransformer extends FfiTransformer { } bool _isStatic(Expression node) { - if (node is! StaticGet) return false; - - return (node as StaticGet).target is Procedure; + if (node is StaticGet) { + return node.target is Procedure; + } + return node is ConstantExpression; } } diff --git a/runtime/bin/ffi_test_functions.cc b/runtime/bin/ffi_test_functions.cc index f1c418ce72f..8e937f025de 100644 --- a/runtime/bin/ffi_test_functions.cc +++ b/runtime/bin/ffi_test_functions.cc @@ -7,15 +7,19 @@ #include #include #include +#include #include "platform/assert.h" #include "platform/globals.h" +#include "vm/os_thread.h" #if defined(HOST_OS_WINDOWS) #include #else #include #endif +#include +#include #include #include @@ -23,6 +27,9 @@ namespace dart { +//////////////////////////////////////////////////////////////////////////////// +// Tests for Dart -> native calls. + // Sums two ints and adds 42. // Simple function to test trampolines. // Also used for testing argument exception on passing null instead of a Dart @@ -449,7 +456,8 @@ DART_EXPORT float InventFloatValue() { return retval; } -// Functions for stress-testing GC by returning values that require boxing. +//////////////////////////////////////////////////////////////////////////////// +// Functions for stress-testing. DART_EXPORT int64_t MinInt64() { return 0x8000000000000000; @@ -511,4 +519,213 @@ DART_EXPORT int RedirectStderr() { } #endif +//////////////////////////////////////////////////////////////////////////////// +// Tests for callbacks. + +#define CHECK(X) \ + if (!(X)) { \ + fprintf(stderr, "%s\n", "Check failed: " #X); \ + return 1; \ + } + +#define CHECK_EQ(X, Y) CHECK((X) == (Y)) + +// Sanity test. +DART_EXPORT int TestSimpleAddition(int (*add)(int, int)) { + CHECK_EQ(add(10, 20), 30); + return 0; +} + +//// Following tests are copied from above, with the role of Dart and C++ code +//// reversed. + +DART_EXPORT int TestIntComputation( + int64_t (*fn)(int8_t, int16_t, int32_t, int64_t)) { + CHECK_EQ(fn(125, 250, 500, 1000), 625); + CHECK_EQ(0x7FFFFFFFFFFFFFFFLL, fn(0, 0, 0, 0x7FFFFFFFFFFFFFFFLL)); + CHECK_EQ(((int64_t)-0x8000000000000000LL), + fn(0, 0, 0, -0x8000000000000000LL)); + return 0; +} + +DART_EXPORT int TestUintComputation( + uint64_t (*fn)(uint8_t, uint16_t, uint32_t, uint64_t)) { + CHECK_EQ(0x7FFFFFFFFFFFFFFFLL, fn(0, 0, 0, 0x7FFFFFFFFFFFFFFFLL)); + CHECK_EQ(-0x8000000000000000LL, fn(0, 0, 0, -0x8000000000000000LL)); + CHECK_EQ(-1, (int64_t)fn(0, 0, 0, -1)); + return 0; +} + +DART_EXPORT int TestSimpleMultiply(double (*fn)(double)) { + CHECK_EQ(fn(2.0), 2.0 * 1.337); + return 0; +} + +DART_EXPORT int TestSimpleMultiplyFloat(float (*fn)(float)) { + CHECK(std::abs(fn(2.0) - 2.0 * 1.337) < 0.001); + return 0; +} + +DART_EXPORT int TestManyInts(intptr_t (*fn)(intptr_t, + intptr_t, + intptr_t, + intptr_t, + intptr_t, + intptr_t, + intptr_t, + intptr_t, + intptr_t, + intptr_t)) { + CHECK_EQ(55, fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); + return 0; +} + +DART_EXPORT int TestManyDoubles(double (*fn)(double, + double, + double, + double, + double, + double, + double, + double, + double, + double)) { + CHECK_EQ(55, fn(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)); + return 0; +} + +DART_EXPORT int TestManyArgs(double (*fn)(intptr_t a, + float b, + intptr_t c, + double d, + intptr_t e, + float f, + intptr_t g, + double h, + intptr_t i, + float j, + intptr_t k, + double l, + intptr_t m, + float n, + intptr_t o, + double p, + intptr_t q, + float r, + intptr_t s, + double t)) { + CHECK(210.0 == fn(1, 2.0, 3, 4.0, 5, 6.0, 7, 8.0, 9, 10.0, 11, 12.0, 13, 14.0, + 15, 16.0, 17, 18.0, 19, 20.0)); + return 0; +} + +DART_EXPORT int TestStore(int64_t* (*fn)(int64_t* a)) { + int64_t p[2] = {42, 1000}; + int64_t* result = fn(p); + CHECK_EQ(*result, 1337); + CHECK_EQ(p[1], 1337); + CHECK_EQ(result, p + 1); + return 0; +} + +DART_EXPORT int TestReturnNull(int32_t fn()) { + CHECK_EQ(fn(), 0); + return 0; +} + +DART_EXPORT int TestNullPointers(int64_t* (*fn)(int64_t* ptr)) { + CHECK_EQ(fn(nullptr), nullptr); + int64_t p[2] = {0}; + CHECK_EQ(fn(p), p + 1); + return 0; +} + +struct CallbackTestData { + int success; + void (*callback)(); +}; + +#if defined(TARGET_OS_LINUX) && !defined(PRODUCT) + +thread_local sigjmp_buf buf; +void CallbackTestSignalHandler(int) { + siglongjmp(buf, 1); +} + +int ExpectAbort(void (*fn)()) { + fprintf(stderr, "**** EXPECT STACKTRACE TO FOLLOW. THIS IS OK. ****\n"); + + struct sigaction old_action; + int result = __sigsetjmp(buf, /*savesigs=*/1); + if (result == 0) { + // Install signal handler. + struct sigaction handler; + handler.sa_handler = CallbackTestSignalHandler; + sigemptyset(&handler.sa_mask); + handler.sa_flags = 0; + + sigaction(SIGABRT, &handler, &old_action); + + fn(); + } else { + // Caught the setjmp. + sigaction(SIGABRT, &old_action, NULL); + exit(0); + } + fprintf(stderr, "Expected abort!!!\n"); + exit(1); +} + +void* TestCallbackOnThreadOutsideIsolate(void* parameter) { + CallbackTestData* data = reinterpret_cast(parameter); + data->success = ExpectAbort(data->callback); + return NULL; +} + +int TestCallbackOtherThreadHelper(void* (*tester)(void*), void (*fn)()) { + CallbackTestData data = {1, fn}; + + pthread_attr_t attr; + int result = pthread_attr_init(&attr); + CHECK_EQ(result, 0); + + pthread_t tid; + result = pthread_create(&tid, &attr, tester, &data); + CHECK_EQ(result, 0); + + result = pthread_attr_destroy(&attr); + CHECK_EQ(result, 0); + + void* retval; + result = pthread_join(tid, &retval); + + // Doesn't actually return because the other thread will exit when the test is + // finished. + UNREACHABLE(); +} + +// Run a callback on another thread and verify that it triggers SIGABRT. +DART_EXPORT int TestCallbackWrongThread(void (*fn)()) { + return TestCallbackOtherThreadHelper(&TestCallbackOnThreadOutsideIsolate, fn); +} + +// Verify that we get SIGABRT when invoking a native callback outside an +// isolate. +DART_EXPORT int TestCallbackOutsideIsolate(void (*fn)()) { + Dart_Isolate current = Dart_CurrentIsolate(); + + Dart_ExitIsolate(); + CallbackTestData data = {1, fn}; + TestCallbackOnThreadOutsideIsolate(&data); + Dart_EnterIsolate(current); + + return data.success; +} + +DART_EXPORT int TestCallbackWrongIsolate(void (*fn)()) { + return ExpectAbort(fn); +} + +#endif // defined(TARGET_OS_LINUX) && !defined(PRODUCT) + } // namespace dart diff --git a/runtime/lib/ffi.cc b/runtime/lib/ffi.cc index 34a553095ef..12620f4886e 100644 --- a/runtime/lib/ffi.cc +++ b/runtime/lib/ffi.cc @@ -9,6 +9,7 @@ #include "vm/class_finalizer.h" #include "vm/compiler/assembler/assembler.h" #include "vm/compiler/ffi.h" +#include "vm/compiler/jit/compiler.h" #include "vm/exceptions.h" #include "vm/log.h" #include "vm/native_arguments.h" @@ -546,53 +547,68 @@ DEFINE_NATIVE_ENTRY(Ffi_asFunction, 1, 1) { return raw_closure; } -// Generates assembly to trampoline from C++ back into Dart. -static void* GenerateFfiInverseTrampoline(const Function& signature, - void* dart_entry_point) { +// Generates assembly to trampoline from native code into Dart. +static uword CompileNativeCallback(const Function& c_signature, + const Function& dart_target) { #if defined(DART_PRECOMPILED_RUNTIME) || defined(DART_PRECOMPILER) UNREACHABLE(); #elif !defined(TARGET_ARCH_X64) // https://github.com/dart-lang/sdk/issues/35774 - UNREACHABLE(); -#elif !defined(TARGET_OS_LINUX) && !defined(TARGET_OS_MACOS) && \ - !defined(TARGET_OS_WINDOWS) - // https://github.com/dart-lang/sdk/issues/35760 Arm32 && Android - // https://github.com/dart-lang/sdk/issues/35772 Arm64 - // https://github.com/dart-lang/sdk/issues/35773 DBC - UNREACHABLE(); + // FFI is supported, but callbacks are not. + Exceptions::ThrowUnsupportedError( + "FFI callbacks are currently supported on 64-bit Intel only."); #else + Thread* const thread = Thread::Current(); + const int32_t callback_id = thread->AllocateFfiCallbackId(); - // TODO(dacoharkes): Implement this. - // https://github.com/dart-lang/sdk/issues/35761 - // Look at StubCode::GenerateInvokeDartCodeStub. - UNREACHABLE(); + // Create a new Function named 'FfiCallback' and stick it in the 'dart:ffi' + // library. Note that these functions will never be invoked by Dart, so it + // doesn't matter that they all have the same name. + Zone* const Z = thread->zone(); + const String& name = + String::ZoneHandle(Symbols::New(Thread::Current(), "FfiCallback")); + const Library& lib = Library::Handle(Library::FfiLibrary()); + const Class& owner_class = Class::Handle(lib.toplevel_class()); + const Function& function = + Function::Handle(Z, Function::New(name, RawFunction::kFfiTrampoline, + /*is_static=*/true, + /*is_const=*/false, + /*is_abstract=*/false, + /*is_external=*/false, + /*is_native=*/false, owner_class, + TokenPosition::kMinSource)); + function.set_is_debuggable(false); + + // Set callback-specific fields which the flow-graph builder needs to generate + // the body. + function.SetFfiCSignature(c_signature); + function.SetFfiCallbackId(callback_id); + function.SetFfiCallbackTarget(dart_target); + + // We compile the callback immediately because we need to return a pointer to + // the entry-point. Native calls do not use patching like Dart calls, so we + // cannot compile it lazily. + const Object& result = + Object::Handle(Z, Compiler::CompileOptimizedFunction(thread, function)); + if (result.IsError()) { + Exceptions::PropagateError(Error::Cast(result)); + } + ASSERT(result.IsCode()); + const Code& code = Code::Cast(result); + + thread->SetFfiCallbackCode(callback_id, code); + + return code.EntryPoint(); #endif } -// TODO(dacoharkes): Implement this feature. -// https://github.com/dart-lang/sdk/issues/35761 -// For now, it always returns Pointer with address 0. DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 1) { GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); GET_NON_NULL_NATIVE_ARGUMENT(Closure, closure, arguments->NativeArgAt(0)); - Function& c_signature = Function::Handle(((Type&)type_arg).signature()); - + const Function& native_signature = + Function::Handle(((Type&)type_arg).signature()); Function& func = Function::Handle(closure.function()); - Code& code = Code::Handle(func.EnsureHasCode()); - void* entryPoint = reinterpret_cast(code.EntryPoint()); - - THR_Print("Ffi_fromFunction: %s\n", type_arg.ToCString()); - THR_Print("Ffi_fromFunction: %s\n", c_signature.ToCString()); - THR_Print("Ffi_fromFunction: %s\n", closure.ToCString()); - THR_Print("Ffi_fromFunction: %s\n", func.ToCString()); - THR_Print("Ffi_fromFunction: %s\n", code.ToCString()); - THR_Print("Ffi_fromFunction: %p\n", entryPoint); - THR_Print("Ffi_fromFunction: %" Pd "\n", code.Size()); - - intptr_t address = reinterpret_cast( - GenerateFfiInverseTrampoline(c_signature, entryPoint)); - TypeArguments& type_args = TypeArguments::Handle(zone); type_args = TypeArguments::New(1); type_args.SetTypeAt(Pointer::kNativeTypeArgPos, type_arg); @@ -608,9 +624,19 @@ DEFINE_NATIVE_ENTRY(Ffi_fromFunction, 1, 1) { ClassFinalizer::FinalizeType(Class::Handle(), native_function_type); native_function_type ^= native_function_type.Canonicalize(); - address = 0; // https://github.com/dart-lang/sdk/issues/35761 + // The FE verifies that the target of a 'fromFunction' is a static method, so + // the value we see here must be a static tearoff. See ffi_use_sites.dart for + // details. + // + // TODO(36748): Define hot-reload semantics of native callbacks. We may need + // to look up the target by name. + ASSERT(func.IsImplicitClosureFunction()); + func = func.parent_function(); + ASSERT(func.is_static()); - Pointer& result = Pointer::Handle(Pointer::New( + const uword address = CompileNativeCallback(native_signature, func); + + const Pointer& result = Pointer::Handle(Pointer::New( native_function_type, Integer::Handle(zone, Integer::New(address)))); return result.raw(); @@ -682,7 +708,7 @@ uint64_t* FfiMarshalledArguments::New( } else if (loc.IsFpuRegister()) { descr.SetFpuRegister(loc.fpu_reg(), arg_value); } else { - ASSERT(loc.IsStackSlot()); + ASSERT(loc.IsStackSlot() || loc.IsDoubleStackSlot()); ASSERT(loc.stack_index() < num_stack_slots); descr.SetStackSlotValue(loc.stack_index(), arg_value); } diff --git a/runtime/lib/ffi_patch.dart b/runtime/lib/ffi_patch.dart index c8fde6c3bbc..6915d51a111 100644 --- a/runtime/lib/ffi_patch.dart +++ b/runtime/lib/ffi_patch.dart @@ -53,3 +53,21 @@ class Pointer { @patch void free() native "Ffi_free"; } + +// This method gets called when an exception bubbles up to the native -> Dart +// boundary from an FFI native callback. Since native code does not have any +// concept of exceptions, the exception cannot be propagated any further. +// Instead, print a warning with the exception and return 0/0.0 from the +// callback. +// +// TODO(36856): Iron out the story behind exceptions. +@pragma("vm:entry-point") +void _handleExposedException(dynamic exception, dynamic stackTrace) { + print( + "==================== UNHANDLED EXCEPTION FROM FFI CALLBACK ===================="); + print( + """ ** Native callbacks should not throw exceptions because they cannot be + propagated into native code. **"""); + print("EXCEPTION: $exception"); + print(stackTrace); +} diff --git a/runtime/vm/compiler/assembler/assembler_ia32.cc b/runtime/vm/compiler/assembler/assembler_ia32.cc index 32656b91993..dc0852861f4 100644 --- a/runtime/vm/compiler/assembler/assembler_ia32.cc +++ b/runtime/vm/compiler/assembler/assembler_ia32.cc @@ -2085,9 +2085,10 @@ void Assembler::ReserveAlignedFrameSpace(intptr_t frame_space) { } void Assembler::TransitionGeneratedToNative(Register destination_address, + Register new_exit_frame, Register scratch) { // Save exit frame information to enable stack walking. - movl(Address(THR, Thread::top_exit_frame_info_offset()), FPREG); + movl(Address(THR, Thread::top_exit_frame_info_offset()), new_exit_frame); // Mark that the thread is executing native code. movl(VMTagAddress(), destination_address); diff --git a/runtime/vm/compiler/assembler/assembler_ia32.h b/runtime/vm/compiler/assembler/assembler_ia32.h index df07d96c61d..8594d6cddfc 100644 --- a/runtime/vm/compiler/assembler/assembler_ia32.h +++ b/runtime/vm/compiler/assembler/assembler_ia32.h @@ -651,6 +651,7 @@ class Assembler : public AssemblerBase { // Require a temporary register 'tmp'. // Clobber all non-CPU registers (e.g. XMM registers and the "FPU stack"). void TransitionGeneratedToNative(Register destination_address, + Register new_exit_frame, Register scratch); void TransitionNativeToGenerated(Register scratch); diff --git a/runtime/vm/compiler/assembler/assembler_x64.cc b/runtime/vm/compiler/assembler/assembler_x64.cc index 073a1790a3d..f9e725b70fa 100644 --- a/runtime/vm/compiler/assembler/assembler_x64.cc +++ b/runtime/vm/compiler/assembler/assembler_x64.cc @@ -164,9 +164,10 @@ void Assembler::setcc(Condition condition, ByteRegister dst) { EmitUint8(0xC0 + (dst & 0x07)); } -void Assembler::TransitionGeneratedToNative(Register destination_address) { +void Assembler::TransitionGeneratedToNative(Register destination_address, + Register new_exit_frame) { // Save exit frame information to enable stack walking. - movq(Address(THR, Thread::top_exit_frame_info_offset()), FPREG); + movq(Address(THR, Thread::top_exit_frame_info_offset()), new_exit_frame); movq(Assembler::VMTagAddress(), destination_address); movq(Address(THR, compiler::target::Thread::execution_state_offset()), @@ -1517,6 +1518,18 @@ void Assembler::ReserveAlignedFrameSpace(intptr_t frame_space) { } } +void Assembler::EmitEntryFrameVerification() { +#if defined(DEBUG) + Label ok; + leaq(RAX, Address(RBP, target::frame_layout.exit_link_slot_from_entry_fp * + target::kWordSize)); + cmpq(RAX, RSP); + j(EQUAL, &ok); + Stop("target::frame_layout.exit_link_slot_from_entry_fp mismatch"); + Bind(&ok); +#endif +} + void Assembler::PushRegisters(intptr_t cpu_register_set, intptr_t xmm_register_set) { const intptr_t xmm_regs_count = RegisterSet::RegisterCount(xmm_register_set); diff --git a/runtime/vm/compiler/assembler/assembler_x64.h b/runtime/vm/compiler/assembler/assembler_x64.h index 1fe5400e454..4df92507b33 100644 --- a/runtime/vm/compiler/assembler/assembler_x64.h +++ b/runtime/vm/compiler/assembler/assembler_x64.h @@ -306,7 +306,8 @@ class Assembler : public AssemblerBase { void setcc(Condition condition, ByteRegister dst); - void TransitionGeneratedToNative(Register destination_address); + void TransitionGeneratedToNative(Register destination_address, + Register new_exit_frame); void TransitionNativeToGenerated(); // Register-register, register-address and address-register instructions. @@ -777,6 +778,13 @@ class Assembler : public AssemblerBase { void LeaveFrame(); void ReserveAlignedFrameSpace(intptr_t frame_space); + // In debug mode, generates code to verify that: + // FP + kExitLinkSlotFromFp == SP + // + // Triggers breakpoint otherwise. + // Clobbers RAX. + void EmitEntryFrameVerification(); + // Create a frame for calling into runtime that preserves all volatile // registers. Frame's RSP is guaranteed to be correctly aligned and // frame_space bytes are reserved under it. diff --git a/runtime/vm/compiler/backend/constant_propagator.cc b/runtime/vm/compiler/backend/constant_propagator.cc index 931ffa30715..44a30557621 100644 --- a/runtime/vm/compiler/backend/constant_propagator.cc +++ b/runtime/vm/compiler/backend/constant_propagator.cc @@ -142,6 +142,10 @@ void ConstantPropagator::VisitFunctionEntry(FunctionEntryInstr* block) { } } +void ConstantPropagator::VisitNativeEntry(NativeEntryInstr* block) { + VisitFunctionEntry(block); +} + void ConstantPropagator::VisitOsrEntry(OsrEntryInstr* block) { for (auto def : *block->initial_definitions()) { def->Accept(this); @@ -192,6 +196,10 @@ void ConstantPropagator::VisitReturn(ReturnInstr* instr) { // Nothing to do. } +void ConstantPropagator::VisitNativeReturn(NativeReturnInstr* instr) { + // Nothing to do. +} + void ConstantPropagator::VisitThrow(ThrowInstr* instr) { // Nothing to do. } @@ -365,6 +373,10 @@ void ConstantPropagator::VisitParameter(ParameterInstr* instr) { SetValue(instr, non_constant_); } +void ConstantPropagator::VisitNativeParameter(NativeParameterInstr* instr) { + SetValue(instr, non_constant_); +} + void ConstantPropagator::VisitPushArgument(PushArgumentInstr* instr) { if (SetValue(instr, instr->value()->definition()->constant_value())) { // The worklist implementation breaks down around push arguments, diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.cc b/runtime/vm/compiler/backend/flow_graph_compiler.cc index 2ef98775dd8..72cbacec4c6 100644 --- a/runtime/vm/compiler/backend/flow_graph_compiler.cc +++ b/runtime/vm/compiler/backend/flow_graph_compiler.cc @@ -1400,7 +1400,7 @@ void FlowGraphCompiler::GenerateStaticCall(intptr_t deopt_id, : ic_data.arguments_descriptor()); ASSERT(ArgumentsDescriptor(arguments_descriptor).TypeArgsLen() == args_info.type_args_len); - if (is_optimizing()) { + if (is_optimizing() && !ForcedOptimization()) { EmitOptimizedStaticCall(function, arguments_descriptor, args_info.count_with_type_args, deopt_id, token_pos, locs, entry_kind); diff --git a/runtime/vm/compiler/backend/flow_graph_compiler.h b/runtime/vm/compiler/backend/flow_graph_compiler.h index 1d9dbd878ab..28c369b5123 100644 --- a/runtime/vm/compiler/backend/flow_graph_compiler.h +++ b/runtime/vm/compiler/backend/flow_graph_compiler.h @@ -409,6 +409,11 @@ class FlowGraphCompiler : public ValueObject { return block_order_; } + // If 'ForcedOptimization()' returns 'true', we are compiling in optimized + // mode for a function which cannot deoptimize. Certain optimizations, e.g. + // speculative optimizations and call patching are disabled. + bool ForcedOptimization() const { return function().ForceOptimize(); } + const FlowGraph& flow_graph() const { return flow_graph_; } BlockEntryInstr* current_block() const { return current_block_; } diff --git a/runtime/vm/compiler/backend/il.cc b/runtime/vm/compiler/backend/il.cc index c554d5ab319..e11efc61992 100644 --- a/runtime/vm/compiler/backend/il.cc +++ b/runtime/vm/compiler/backend/il.cc @@ -27,6 +27,7 @@ #include "vm/regexp_assembler_ir.h" #include "vm/resolver.h" #include "vm/scopes.h" +#include "vm/stack_frame.h" #include "vm/stub_code.h" #include "vm/symbols.h" #include "vm/type_testing_stubs.h" @@ -3900,7 +3901,9 @@ void FunctionEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ nop(); } #endif - __ Bind(compiler->GetJumpLabel(this)); + if (tag() == Instruction::kFunctionEntry) { + __ Bind(compiler->GetJumpLabel(this)); + } // In the AOT compiler we want to reduce code size, so generate no // fall-through code in [FlowGraphCompiler::CompileGraph()]. @@ -3952,6 +3955,21 @@ void FunctionEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { } } +LocationSummary* NativeEntryInstr::MakeLocationSummary(Zone* zone, + bool optimizing) const { + UNREACHABLE(); +} + +#if !defined(TARGET_ARCH_X64) +void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { + UNREACHABLE(); +} + +void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) { + UNREACHABLE(); +} +#endif + LocationSummary* OsrEntryInstr::MakeLocationSummary(Zone* zone, bool optimizing) const { UNREACHABLE(); @@ -4057,6 +4075,43 @@ void ParameterInstr::EmitNativeCode(FlowGraphCompiler* compiler) { UNREACHABLE(); } +void NativeParameterInstr::EmitNativeCode(FlowGraphCompiler* compiler) { +#if !defined(TARGET_ARCH_DBC) + // The native entry frame has size -kExitLinkSlotFromFp. In order to access + // the top of stack from above the entry frame, we add a constant to account + // for the the two frame pointers and return address of the entry frame. + constexpr intptr_t kEntryFramePadding = 3; + FrameRebase rebase(/*old_base=*/SPREG, /*new_base=*/FPREG, + -kExitLinkSlotFromEntryFp + kEntryFramePadding); + const Location dst = locs()->out(0); + const Location src = rebase.Rebase(loc_); + NoTemporaryAllocator no_temp; + compiler->EmitMove(dst, src, &no_temp); +#else + UNREACHABLE(); +#endif +} + +LocationSummary* NativeParameterInstr::MakeLocationSummary(Zone* zone, + bool opt) const { +#if !defined(TARGET_ARCH_DBC) + ASSERT(opt); + Location input = Location::Any(); + if (representation() == kUnboxedInt64 && compiler::target::kWordSize < 8) { + input = Location::Pair(Location::RequiresRegister(), + Location::RequiresFpuRegister()); + } else { + input = RegisterKindForResult() == Location::kRegister + ? Location::RequiresRegister() + : Location::RequiresFpuRegister(); + } + return LocationSummary::Make(zone, /*num_inputs=*/0, input, + LocationSummary::kNoCall); +#else + UNREACHABLE(); +#endif +} + bool ParallelMoveInstr::IsRedundant() const { for (intptr_t i = 0; i < moves_.length(); i++) { if (!moves_[i]->IsRedundant()) { @@ -5445,6 +5500,16 @@ Location FfiCallInstr::UnallocateStackSlots(Location in, bool is_atomic) { } } +LocationSummary* NativeReturnInstr::MakeLocationSummary(Zone* zone, + bool opt) const { + const intptr_t kNumInputs = 1; + const intptr_t kNumTemps = 0; + LocationSummary* locs = new (zone) + LocationSummary(zone, kNumInputs, kNumTemps, LocationSummary::kNoCall); + locs->set_in(0, result_location_); + return locs; +} + #undef Z #else @@ -5467,6 +5532,11 @@ LocationSummary* FfiCallInstr::MakeLocationSummary(Zone* zone, return summary; } +LocationSummary* NativeReturnInstr::MakeLocationSummary(Zone* zone, + bool opt) const { + UNREACHABLE(); +} + #endif // !defined(TARGET_ARCH_DBC) Representation FfiCallInstr::representation() const { diff --git a/runtime/vm/compiler/backend/il.h b/runtime/vm/compiler/backend/il.h index f2be34a6d6f..012ebadaf5c 100644 --- a/runtime/vm/compiler/backend/il.h +++ b/runtime/vm/compiler/backend/il.h @@ -336,18 +336,21 @@ struct InstrAttrs { M(JoinEntry, kNoGC) \ M(TargetEntry, kNoGC) \ M(FunctionEntry, kNoGC) \ + M(NativeEntry, kNoGC) \ M(OsrEntry, kNoGC) \ M(IndirectEntry, kNoGC) \ M(CatchBlockEntry, kNoGC) \ M(Phi, kNoGC) \ M(Redefinition, kNoGC) \ M(Parameter, kNoGC) \ + M(NativeParameter, kNoGC) \ M(LoadIndexedUnsafe, kNoGC) \ M(StoreIndexedUnsafe, kNoGC) \ M(TailCall, kNoGC) \ M(ParallelMove, kNoGC) \ M(PushArgument, kNoGC) \ M(Return, kNoGC) \ + M(NativeReturn, kNoGC) \ M(Throw, kNoGC) \ M(ReThrow, kNoGC) \ M(Stop, _) \ @@ -920,6 +923,28 @@ class Instruction : public ZoneAllocated { virtual bool UseSharedSlowPathStub(bool is_optimizing) const { return false; } + // 'RegisterKindForResult()' returns the register kind necessary to hold the + // result. + // + // This is not virtual because instructions should override representation() + // instead. + Location::Kind RegisterKindForResult() const { + const Representation rep = representation(); +#if !defined(TARGET_ARCH_DBC) + if ((rep == kUnboxedFloat) || (rep == kUnboxedDouble) || + (rep == kUnboxedFloat32x4) || (rep == kUnboxedInt32x4) || + (rep == kUnboxedFloat64x2)) { + return Location::kFpuRegister; + } +#else + // DBC supports only unboxed doubles and does not have distinguished FPU + // registers. + ASSERT((rep != kUnboxedFloat32x4) && (rep != kUnboxedInt32x4) && + (rep != kUnboxedFloat64x2)); +#endif + return Location::kRegister; + } + protected: // GetDeoptId and/or CopyDeoptIdFrom. friend class CallSiteInliner; @@ -1618,6 +1643,33 @@ class FunctionEntryInstr : public BlockEntryWithInitialDefs { DISALLOW_COPY_AND_ASSIGN(FunctionEntryInstr); }; +// Represents entry into a function from native code. +// +// Native entries are not allowed to have regular parameters. They should use +// NativeParameter instead (which doesn't count as an initial definition). +class NativeEntryInstr : public FunctionEntryInstr { + public: + NativeEntryInstr(const ZoneGrowableArray* argument_locations, + GraphEntryInstr* graph_entry, + intptr_t block_id, + intptr_t try_index, + intptr_t deopt_id, + intptr_t callback_id) + : FunctionEntryInstr(graph_entry, block_id, try_index, deopt_id), + callback_id_(callback_id), + argument_locations_(argument_locations) {} + + DECLARE_INSTRUCTION(NativeEntry) + + PRINT_TO_SUPPORT + + private: + void SaveArgument(FlowGraphCompiler* compiler, Location loc) const; + + const intptr_t callback_id_; + const ZoneGrowableArray* const argument_locations_; +}; + // Represents an OSR entrypoint to a function. // // The OSR entry has it's own initial definitions. @@ -2197,6 +2249,57 @@ class ParameterInstr : public Definition { DISALLOW_COPY_AND_ASSIGN(ParameterInstr); }; +// Native parameters are not treated as initial definitions because they cannot +// be inlined and are only usable in optimized code. The location must be a +// stack location relative to the position of the stack (SPREG) after +// register-based arguments have been saved on entry to a native call. See +// NativeEntryInstr::EmitNativeCode for more details. +// +// TOOD(33549): Unify with ParameterInstr. +class NativeParameterInstr : public Definition { + public: + NativeParameterInstr(Location loc, Representation representation) + : loc_(loc), representation_(representation) { + if (loc.IsPairLocation()) { + for (intptr_t i : {0, 1}) { + ASSERT(loc_.Component(i).HasStackIndex() && + loc_.Component(i).base_reg() == SPREG); + } + } else { + ASSERT(loc_.HasStackIndex() && loc_.base_reg() == SPREG); + } + } + + DECLARE_INSTRUCTION(NativeParameter) + + virtual Representation representation() const { return representation_; } + + intptr_t InputCount() const { return 0; } + Value* InputAt(intptr_t i) const { + UNREACHABLE(); + return NULL; + } + + virtual bool ComputeCanDeoptimize() const { return false; } + + virtual bool HasUnknownSideEffects() const { return false; } + + // TODO(sjindel): We can make this more precise. + virtual CompileType ComputeType() const { return CompileType::Dynamic(); } + + virtual bool MayThrow() const { return false; } + + PRINT_OPERANDS_TO_SUPPORT + + private: + virtual void RawSetInputAt(intptr_t i, Value* value) { UNREACHABLE(); } + + const Location loc_; + const Representation representation_; + + DISALLOW_COPY_AND_ASSIGN(NativeParameterInstr); +}; + // Stores a tagged pointer to a slot accessible from a fixed register. It has // the form: // @@ -2260,8 +2363,11 @@ class StoreIndexedUnsafeInstr : public TemplateInstruction<2, NoThrow> { // the frame. This is asserted via `inliner.cc::CalleeGraphValidator`. class LoadIndexedUnsafeInstr : public TemplateDefinition<1, NoThrow> { public: - LoadIndexedUnsafeInstr(Value* index, intptr_t offset, CompileType result_type) - : offset_(offset) { + LoadIndexedUnsafeInstr(Value* index, + intptr_t offset, + CompileType result_type, + Representation representation = kTagged) + : offset_(offset), representation_(representation) { UpdateType(result_type); SetInputAt(0, index); } @@ -2272,7 +2378,6 @@ class LoadIndexedUnsafeInstr : public TemplateDefinition<1, NoThrow> { ASSERT(index == 0); return kTagged; } - virtual Representation representation() const { return kTagged; } virtual bool ComputeCanDeoptimize() const { return false; } virtual bool HasUnknownSideEffects() const { return false; } @@ -2288,6 +2393,7 @@ class LoadIndexedUnsafeInstr : public TemplateDefinition<1, NoThrow> { private: const intptr_t offset_; + const Representation representation_; DISALLOW_COPY_AND_ASSIGN(LoadIndexedUnsafeInstr); }; @@ -2397,6 +2503,40 @@ class ReturnInstr : public TemplateInstruction<1, NoThrow> { DISALLOW_COPY_AND_ASSIGN(ReturnInstr); }; +// Represents a return from a Dart function into native code. +class NativeReturnInstr : public ReturnInstr { + public: + NativeReturnInstr(TokenPosition token_pos, + Value* value, + Representation rep, + Location result_location, + intptr_t deopt_id) + : ReturnInstr(token_pos, value, deopt_id), + result_representation_(rep), + result_location_(result_location) {} + + DECLARE_INSTRUCTION(NativeReturn) + + PRINT_OPERANDS_TO_SUPPORT + + virtual Representation RequiredInputRepresentation(intptr_t idx) const { + ASSERT(idx == 0); + return result_representation_; + } + + virtual bool CanBecomeDeoptimizationTarget() const { + // Unlike ReturnInstr, NativeReturnInstr cannot be inlined (because it's + // returning into native code). + return false; + } + + private: + const Representation result_representation_; + const Location result_location_; + + DISALLOW_COPY_AND_ASSIGN(NativeReturnInstr); +}; + class ThrowInstr : public TemplateInstruction<0, Throws> { public: explicit ThrowInstr(TokenPosition token_pos, intptr_t deopt_id) diff --git a/runtime/vm/compiler/backend/il_ia32.cc b/runtime/vm/compiler/backend/il_ia32.cc index 403ca00184d..e9132278ccd 100644 --- a/runtime/vm/compiler/backend/il_ia32.cc +++ b/runtime/vm/compiler/backend/il_ia32.cc @@ -886,7 +886,7 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ popl(tmp); __ movl(Address(FPREG, kSavedCallerPcSlotFromFp * kWordSize), tmp); - __ TransitionGeneratedToNative(branch, tmp); + __ TransitionGeneratedToNative(branch, FPREG, tmp); __ call(branch); // The x86 calling convention requires floating point values to be returned on diff --git a/runtime/vm/compiler/backend/il_printer.cc b/runtime/vm/compiler/backend/il_printer.cc index fe7508fe17b..a2f841d3dc8 100644 --- a/runtime/vm/compiler/backend/il_printer.cc +++ b/runtime/vm/compiler/backend/il_printer.cc @@ -524,12 +524,10 @@ void ClosureCallInstr::PrintOperandsTo(BufferFormatter* f) const { void FfiCallInstr::PrintOperandsTo(BufferFormatter* f) const { f->Print(" pointer="); InputAt(TargetAddressIndex())->PrintTo(f); - f->Print(" signature=%s", - Type::Handle(signature_.SignatureType()).ToCString()); for (intptr_t i = 0, n = InputCount(); i < n - 1; ++i) { f->Print(", "); InputAt(i)->PrintTo(f); - f->Print(" (at %s) ", arg_locations_[i].ToCString()); + f->Print(" (@%s)", arg_locations_[i].ToCString()); } } @@ -1063,6 +1061,24 @@ void FunctionEntryInstr::PrintTo(BufferFormatter* f) const { BlockEntryWithInitialDefs::PrintInitialDefinitionsTo(f); } +void NativeEntryInstr::PrintTo(BufferFormatter* f) const { + f->Print("B%" Pd "[native function entry]:%" Pd, block_id(), GetDeoptId()); + if (HasParallelMove()) { + f->Print("\n"); + parallel_move()->PrintTo(f); + } + BlockEntryWithInitialDefs::PrintInitialDefinitionsTo(f); +} + +void NativeReturnInstr::PrintOperandsTo(BufferFormatter* f) const { + value()->PrintTo(f); +} + +void NativeParameterInstr::PrintOperandsTo(BufferFormatter* f) const { + f->Print("%s as %s", loc_.ToCString(), + RepresentationToCString(representation_)); +} + void CatchBlockEntryInstr::PrintTo(BufferFormatter* f) const { f->Print("B%" Pd "[target catch try_idx %" Pd " catch_try_idx %" Pd "]", block_id(), try_index(), catch_try_index()); diff --git a/runtime/vm/compiler/backend/il_x64.cc b/runtime/vm/compiler/backend/il_x64.cc index e8bf0f89a4f..e9e83edbcd2 100644 --- a/runtime/vm/compiler/backend/il_x64.cc +++ b/runtime/vm/compiler/backend/il_x64.cc @@ -139,6 +139,43 @@ void ReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ set_constant_pool_allowed(true); } +void NativeReturnInstr::EmitNativeCode(FlowGraphCompiler* compiler) { + __ LeaveDartFrame(); + + // Pop dummy return address. + __ popq(TMP); + + // Anything besides the return register. + const Register vm_tag_reg = RBX, old_exit_frame_reg = RCX; + + __ popq(old_exit_frame_reg); + + // Restore top_resource. + __ popq(TMP); + __ movq(Address(THR, compiler::target::Thread::top_resource_offset()), TMP); + + __ popq(vm_tag_reg); + + // TransitionGeneratedToNative will reset the exit frame info to + // old_exit_frame_reg *before* entering the safepoint. + __ TransitionGeneratedToNative(vm_tag_reg, old_exit_frame_reg); + + // Restore C++ ABI callee-saved registers. + __ PopRegisters(CallingConventions::kCalleeSaveCpuRegisters, + CallingConventions::kCalleeSaveXmmRegisters); + + // Leave the entry frame. + __ LeaveFrame(); + + // Leave the dummy frame holding the pushed arguments. + __ LeaveFrame(); + + __ ret(); + + // For following blocks. + __ set_constant_pool_allowed(true); +} + static Condition NegateCondition(Condition condition) { switch (condition) { case EQUAL: @@ -921,7 +958,7 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ movq(Address(FPREG, kSavedCallerPcSlotFromFp * kWordSize), TMP); // Update information in the thread object and enter a safepoint. - __ TransitionGeneratedToNative(target_address); + __ TransitionGeneratedToNative(target_address, FPREG); __ CallCFunction(target_address); @@ -943,6 +980,130 @@ void FfiCallInstr::EmitNativeCode(FlowGraphCompiler* compiler) { __ popq(TMP); } +void NativeEntryInstr::SaveArgument(FlowGraphCompiler* compiler, + Location loc) const { + ASSERT(!loc.IsPairLocation()); + + if (loc.HasStackIndex()) return; + + if (loc.IsRegister()) { + __ pushq(loc.reg()); + } else if (loc.IsFpuRegister()) { + __ movq(TMP, loc.fpu_reg()); + __ pushq(TMP); + } else { + UNREACHABLE(); + } +} + +void NativeEntryInstr::EmitNativeCode(FlowGraphCompiler* compiler) { + if (FLAG_precompiled_mode) { + UNREACHABLE(); + } + + __ Bind(compiler->GetJumpLabel(this)); + + // Create a dummy frame holding the pushed arguments. This simplifies + // NativeReturnInstr::EmitNativeCode. + __ EnterFrame(0); + + // Save the argument registers, in reverse order. + for (intptr_t i = argument_locations_->length(); i-- > 0;) { + SaveArgument(compiler, argument_locations_->At(i)); + } + + // Enter the entry frame. + __ EnterFrame(0); + + // Save a space for the code object. + __ PushImmediate(Immediate(0)); + + // InvokoeDartCodeStub saves the arguments descriptor here. We don't have one, + // but we need to follow the same frame layout for the stack walker. + __ PushImmediate(Immediate(0)); + + // Save ABI callee-saved registers. + __ PushRegisters(CallingConventions::kCalleeSaveCpuRegisters, + CallingConventions::kCalleeSaveXmmRegisters); + + // Load the thread object. + // TODO(35765): Fix linking issue on AOT. + // TOOD(35934): Exclude native callbacks from snapshots. + // + // Create another frame to align the frame before continuing in "native" code. + { + __ EnterFrame(0); + __ ReserveAlignedFrameSpace(0); + + __ movq( + RAX, + Immediate(reinterpret_cast(DLRT_GetThreadForNativeCallback))); + __ call(RAX); + __ movq(THR, RAX); + + __ LeaveFrame(); + } + + // Save the current VMTag on the stack. + __ movq(RAX, Assembler::VMTagAddress()); + __ pushq(RAX); + + // Save top resource. + __ pushq(Address(THR, compiler::target::Thread::top_resource_offset())); + __ movq(Address(THR, compiler::target::Thread::top_resource_offset()), + Immediate(0)); + + // Save top exit frame info. Stack walker expects it to be here. + __ pushq( + Address(THR, compiler::target::Thread::top_exit_frame_info_offset())); + + // In debug mode, verify that we've pushed the top exit frame info at the + // correct offset from FP. + __ EmitEntryFrameVerification(); + + // TransitionNativeToGenerated will reset top exit frame info to 0 *after* + // leaving the safepoint. + __ TransitionNativeToGenerated(); + + // Now that the safepoint has ended, we can touch Dart objects without + // handles. + // Otherwise we'll clobber the argument sent from the caller. + COMPILE_ASSERT(RAX != CallingConventions::kArg1Reg); + __ movq(CallingConventions::kArg1Reg, Immediate(callback_id_)); + __ movq(RAX, Address(THR, compiler::target::Thread:: + verify_callback_isolate_entry_point_offset())); + __ call(RAX); + + // Load the code object. + __ movq(RAX, Address(THR, compiler::target::Thread::callback_code_offset())); + __ movq(RAX, FieldAddress( + RAX, compiler::target::GrowableObjectArray::data_offset())); + __ movq(CODE_REG, + FieldAddress(RAX, compiler::target::Array::data_offset() + + callback_id_ * compiler::target::kWordSize)); + + // Put the code object in the reserved slot. + __ movq(Address(FPREG, kPcMarkerSlotFromFp * compiler::target::kWordSize), + CODE_REG); + + if (FLAG_precompiled_mode && FLAG_use_bare_instructions) { + __ movq(PP, Address(THR, + compiler::target::Thread::global_object_pool_offset())); + } else { + __ xorq(PP, PP); // GC-safe value into PP. + } + + // Push a dummy return address which suggests that we are inside of + // InvokeDartCodeStub. This is how the stack walker detects an entry frame. + __ movq( + RAX, + Address(THR, compiler::target::Thread::invoke_dart_code_stub_offset())); + __ pushq(FieldAddress(RAX, compiler::target::Code::entry_point_offset())); + + // Continue with Dart frame setup. + FunctionEntryInstr::EmitNativeCode(compiler); +} + static bool CanBeImmediateIndex(Value* index, intptr_t cid) { if (!index->definition()->IsConstant()) return false; const Object& constant = index->definition()->AsConstant()->value(); @@ -1665,10 +1826,10 @@ void GuardFieldClassInstr::EmitNativeCode(FlowGraphCompiler* compiler) { Label ok, fail_label; - Label* deopt = - compiler->is_optimizing() - ? compiler->AddDeoptStub(deopt_id(), ICData::kDeoptGuardField) - : NULL; + Label* deopt = NULL; + if (compiler->is_optimizing()) { + deopt = compiler->AddDeoptStub(deopt_id(), ICData::kDeoptGuardField); + } Label* fail = (deopt != NULL) ? deopt : &fail_label; diff --git a/runtime/vm/compiler/backend/linearscan.cc b/runtime/vm/compiler/backend/linearscan.cc index 4f5473cf2cd..b5fea9c9abe 100644 --- a/runtime/vm/compiler/backend/linearscan.cc +++ b/runtime/vm/compiler/backend/linearscan.cc @@ -824,25 +824,6 @@ static Location::Kind RegisterKindFromPolicy(Location loc) { } } -static Location::Kind RegisterKindForResult(Instruction* instr) { - const Representation rep = instr->representation(); -#if !defined(TARGET_ARCH_DBC) - if ((rep == kUnboxedFloat) || (rep == kUnboxedDouble) || - (rep == kUnboxedFloat32x4) || (rep == kUnboxedInt32x4) || - (rep == kUnboxedFloat64x2)) { - return Location::kFpuRegister; - } else { - return Location::kRegister; - } -#else - // DBC supports only unboxed doubles and does not have distinguished FPU - // registers. - ASSERT((rep != kUnboxedFloat32x4) && (rep != kUnboxedInt32x4) && - (rep != kUnboxedFloat64x2)); - return Location::kRegister; -#endif -} - // // When describing shape of live ranges in comments below we are going to use // the following notation: @@ -991,11 +972,11 @@ void FlowGraphAllocator::ConnectIncomingPhiMoves(JoinEntryInstr* join) { // All phi resolution moves are connected. Phi's live range is // complete. AssignSafepoints(phi, range); - CompleteRange(range, RegisterKindForResult(phi)); + CompleteRange(range, phi->RegisterKindForResult()); if (is_pair_phi) { LiveRange* second_range = GetLiveRange(ToSecondPairVreg(vreg)); AssignSafepoints(phi, second_range); - CompleteRange(second_range, RegisterKindForResult(phi)); + CompleteRange(second_range, phi->RegisterKindForResult()); } move_idx += is_pair_phi ? 2 : 1; @@ -1303,7 +1284,7 @@ void FlowGraphAllocator::ProcessOneOutput(BlockEntryInstr* block, } AssignSafepoints(def, range); - CompleteRange(range, RegisterKindForResult(def)); + CompleteRange(range, def->RegisterKindForResult()); } // Create and update live ranges corresponding to instruction's inputs, diff --git a/runtime/vm/compiler/ffi.cc b/runtime/vm/compiler/ffi.cc index 823e8ef7b50..665382a1f1f 100644 --- a/runtime/vm/compiler/ffi.cc +++ b/runtime/vm/compiler/ffi.cc @@ -9,6 +9,8 @@ #include "platform/globals.h" #include "vm/compiler/backend/locations.h" #include "vm/compiler/runtime_api.h" +#include "vm/growable_array.h" +#include "vm/stack_frame.h" namespace dart { @@ -182,7 +184,7 @@ template -class ArgumentFrameState : public ValueObject { +class ArgumentAllocator : public ValueObject { public: Location AllocateArgument(Representation rep) { switch (rep) { @@ -207,9 +209,13 @@ class ArgumentFrameState : public ValueObject { } // Argument must be spilled. - if ((rep == kUnboxedInt64 || rep == kUnboxedDouble) && - compiler::target::kWordSize == 4) { + if (rep == kUnboxedInt64 && compiler::target::kWordSize == 4) { return AllocateAlignedStackSlots(rep); + } else if (rep == kUnboxedDouble) { + // By convention, we always use DoubleStackSlot for doubles, even on + // 64-bit systems. + ASSERT(!CallingConventions::kAlignArguments); + return AllocateDoubleStackSlot(); } else { return AllocateStackSlot(); } @@ -221,6 +227,13 @@ class ArgumentFrameState : public ValueObject { CallingConventions::kStackPointerRegister); } + Location AllocateDoubleStackSlot() { + const Location result = Location::DoubleStackSlot( + stack_height_in_slots, CallingConventions::kStackPointerRegister); + stack_height_in_slots += 8 / compiler::target::kWordSize; + return result; + } + // Allocates a pair of stack slots where the first stack slot is aligned to an // 8-byte boundary, if necessary. Location AllocateAlignedStackSlots(Representation rep) { @@ -287,6 +300,68 @@ class ArgumentFrameState : public ValueObject { intptr_t stack_height_in_slots = 0; }; +ZoneGrowableArray* +CallbackArgumentTranslator::TranslateArgumentLocations( + const ZoneGrowableArray& arg_locs) { + auto& pushed_locs = *(new ZoneGrowableArray(arg_locs.length())); + + CallbackArgumentTranslator translator; + for (intptr_t i = 0, n = arg_locs.length(); i < n; i++) { + translator.AllocateArgument(arg_locs[i]); + } + for (intptr_t i = 0, n = arg_locs.length(); i < n; ++i) { + pushed_locs.Add(translator.TranslateArgument(arg_locs[i])); + } + + return &pushed_locs; +} + +void CallbackArgumentTranslator::AllocateArgument(Location arg) { + if (arg.IsPairLocation()) { + AllocateArgument(arg.Component(0)); + AllocateArgument(arg.Component(1)); + return; + } + if (arg.HasStackIndex()) return; + ASSERT(arg.IsRegister() || arg.IsFpuRegister()); + if (arg.IsRegister()) { + argument_slots_required_++; + } else { + argument_slots_required_ += 8 / compiler::target::kWordSize; + } +} + +Location CallbackArgumentTranslator::TranslateArgument(Location arg) { + if (arg.IsPairLocation()) { + const Location low = TranslateArgument(arg.Component(0)); + const Location high = TranslateArgument(arg.Component(1)); + return Location::Pair(low, high); + } + + if (arg.HasStackIndex()) { + // Add extra slots after the saved arguments for the return address and + // frame pointer of the dummy arguments frame, which will be between the + // saved argument registers and stack arguments. Also add slots for the + // shadow space if present (factored into + // kCallbackSlotsBeforeSavedArguments). + FrameRebase rebase( + /*old_base=*/SPREG, /*new_base=*/SPREG, + /*stack_delta=*/argument_slots_required_ + + kCallbackSlotsBeforeSavedArguments); + return rebase.Rebase(arg); + } + + if (arg.IsRegister()) { + return Location::StackSlot(argument_slots_used_++, SPREG); + } + + ASSERT(arg.IsFpuRegister()); + const Location result = + Location::DoubleStackSlot(argument_slots_used_, SPREG); + argument_slots_used_ += 8 / compiler::target::kWordSize; + return result; +} + // Takes a list of argument representations, and converts it to a list of // argument locations based on calling convention. template * ArgumentLocationsBase( auto result = new ZoneGrowableArray(num_arguments); // Loop through all arguments and assign a register or a stack location. - ArgumentFrameState + ArgumentAllocator frame_state; for (intptr_t i = 0; i < num_arguments; i++) { Representation rep = arg_reps[i]; diff --git a/runtime/vm/compiler/ffi.h b/runtime/vm/compiler/ffi.h index cd8e96fc62b..846775a6ba6 100644 --- a/runtime/vm/compiler/ffi.h +++ b/runtime/vm/compiler/ffi.h @@ -108,6 +108,31 @@ class FfiSignatureDescriptor : public ValueObject { #endif // defined(TARGET_ARCH_DBC) +// This classes translates the ABI location of arguments into the locations they +// will inhabit after entry-frame setup in the invocation of a native callback. +// +// Native -> Dart callbacks must push all the arguments before executing any +// Dart code because the reading the Thread from TLS requires calling a native +// stub, and the argument registers are volatile on all ABIs we support. +// +// To avoid complicating initial definitions, all callback arguments are read +// off the stack from their pushed locations, so this class updates the argument +// positions to account for this. +// +// See 'NativeEntryInstr::EmitNativeCode' for details. +class CallbackArgumentTranslator : public ValueObject { + public: + static ZoneGrowableArray* TranslateArgumentLocations( + const ZoneGrowableArray& arg_locs); + + private: + void AllocateArgument(Location arg); + Location TranslateArgument(Location arg); + + intptr_t argument_slots_used_ = 0; + intptr_t argument_slots_required_ = 0; +}; + } // namespace ffi } // namespace compiler diff --git a/runtime/vm/compiler/frontend/kernel_to_il.cc b/runtime/vm/compiler/frontend/kernel_to_il.cc index 485e7e7889a..e2e5fded115 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.cc +++ b/runtime/vm/compiler/frontend/kernel_to_il.cc @@ -16,6 +16,7 @@ #include "vm/compiler/jit/compiler.h" #include "vm/kernel_loader.h" #include "vm/longjump.h" +#include "vm/native_entry.h" #include "vm/object_store.h" #include "vm/report.h" #include "vm/resolver.h" @@ -2440,6 +2441,31 @@ Fragment FlowGraphBuilder::FfiUnboxedExtend(Representation representation, return Fragment(extend); } +Fragment FlowGraphBuilder::FfiExceptionalReturnValue( + const AbstractType& result_type, + Representation representation) { + ASSERT(optimizing_); + Object& result = Object::ZoneHandle(Z, Object::null()); + if (representation == kUnboxedFloat || representation == kUnboxedDouble) { + result = Double::New(0.0, Heap::kOld); + } else { + result = Integer::New(0, Heap::kOld); + } + Fragment code; + code += Constant(result); + code += UnboxTruncate(representation); + return code; +} + +#if !defined(TARGET_ARCH_DBC) +Fragment FlowGraphBuilder::NativeReturn(Representation result) { + auto* instr = new (Z) + NativeReturnInstr(TokenPosition::kNoSource, Pop(), result, + compiler::ffi::ResultLocation(result), DeoptId::kNone); + return Fragment(instr); +} +#endif + Fragment FlowGraphBuilder::FfiPointerFromAddress(const Type& result_type) { Fragment test; TargetEntryInstr* null_entry; @@ -2498,8 +2524,69 @@ Fragment FlowGraphBuilder::BitCast(Representation from, Representation to) { return Fragment(instr); } +Fragment FlowGraphBuilder::FfiConvertArgumentToDart( + const AbstractType& ffi_type, + const Representation native_representation) { + Fragment body; + if (compiler::ffi::NativeTypeIsPointer(ffi_type)) { + body += Box(kUnboxedFfiIntPtr); + body += FfiPointerFromAddress(Type::Cast(ffi_type)); + } else if (compiler::ffi::NativeTypeIsVoid(ffi_type)) { + body += Drop(); + body += NullConstant(); + } else { + const Representation from_rep = native_representation; + const Representation to_rep = compiler::ffi::TypeRepresentation(ffi_type); + if (from_rep != to_rep) { + body += BitCast(from_rep, to_rep); + } else { + body += FfiUnboxedExtend(from_rep, ffi_type); + } + body += Box(to_rep); + } + return body; +} + +Fragment FlowGraphBuilder::FfiConvertArgumentToNative( + const Function& function, + const AbstractType& ffi_type, + const Representation native_representation) { + Fragment body; + // Check for 'null'. Only ffi.Pointers are allowed to be null. + if (!compiler::ffi::NativeTypeIsPointer(ffi_type)) { + body += LoadLocal(MakeTemporary()); + body <<= + new (Z) CheckNullInstr(Pop(), String::ZoneHandle(Z, function.name()), + GetNextDeoptId(), TokenPosition::kNoSource); + } + + if (compiler::ffi::NativeTypeIsPointer(ffi_type)) { + body += LoadAddressFromFfiPointer(); + body += UnboxTruncate(kUnboxedFfiIntPtr); + } else { + Representation from_rep = compiler::ffi::TypeRepresentation(ffi_type); + body += UnboxTruncate(from_rep); + + Representation to_rep = native_representation; + if (from_rep != to_rep) { + body += BitCast(from_rep, to_rep); + } else { + body += FfiUnboxedExtend(from_rep, ffi_type); + } + } + return body; +} + FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline( const Function& function) { + if (function.FfiCallbackTarget() != Function::null()) { + return BuildGraphOfFfiCallback(function); + } else { + return BuildGraphOfFfiNative(function); + } +} + +FlowGraph* FlowGraphBuilder::BuildGraphOfFfiNative(const Function& function) { graph_entry_ = new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); @@ -2532,29 +2619,7 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline( for (intptr_t pos = 1; pos < function.num_fixed_parameters(); pos++) { body += LoadLocal(parsed_function_->ParameterVariable(pos)); ffi_type = signature.ParameterTypeAt(pos); - - // Check for 'null'. Only ffi.Pointers are allowed to be null. - if (!compiler::ffi::NativeTypeIsPointer(ffi_type)) { - body += LoadLocal(parsed_function_->ParameterVariable(pos)); - body <<= - new (Z) CheckNullInstr(Pop(), String::ZoneHandle(Z, function.name()), - GetNextDeoptId(), TokenPosition::kNoSource); - } - - if (compiler::ffi::NativeTypeIsPointer(ffi_type)) { - body += LoadAddressFromFfiPointer(); - body += UnboxTruncate(kUnboxedFfiIntPtr); - } else { - Representation from_rep = compiler::ffi::TypeRepresentation(ffi_type); - body += UnboxTruncate(from_rep); - - Representation to_rep = arg_reps[pos - 1]; - if (from_rep != to_rep) { - body += BitCast(from_rep, to_rep); - } else { - body += FfiUnboxedExtend(from_rep, ffi_type); - } - } + body += FfiConvertArgumentToNative(function, ffi_type, arg_reps[pos - 1]); } // Push the function pointer, which is stored (boxed) in the first slot of the @@ -2569,34 +2634,109 @@ FlowGraph* FlowGraphBuilder::BuildGraphOfFfiTrampoline( body += FfiCall(signature, arg_reps, arg_locs, arg_host_locs); ffi_type = signature.result_type(); - if (compiler::ffi::NativeTypeIsPointer(ffi_type)) { - body += Box(kUnboxedFfiIntPtr); - body += FfiPointerFromAddress(Type::Cast(ffi_type)); - } else if (compiler::ffi::NativeTypeIsVoid(ffi_type)) { - body += Drop(); - body += NullConstant(); - } else { #if !defined(TARGET_ARCH_DBC) - Representation from_rep = compiler::ffi::ResultRepresentation(signature); + const Representation from_rep = + compiler::ffi::ResultRepresentation(signature); #else - Representation from_rep = - compiler::ffi::ResultHostRepresentation(signature); + const Representation from_rep = + compiler::ffi::ResultHostRepresentation(signature); #endif // !defined(TARGET_ARCH_DBC) - Representation to_rep = compiler::ffi::TypeRepresentation(ffi_type); - if (from_rep != to_rep) { - body += BitCast(from_rep, to_rep); - } else { - body += FfiUnboxedExtend(from_rep, ffi_type); - } - body += Box(to_rep); - } - + body += FfiConvertArgumentToDart(ffi_type, from_rep); body += Return(TokenPosition::kNoSource); return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, prologue_info); } +FlowGraph* FlowGraphBuilder::BuildGraphOfFfiCallback(const Function& function) { +#if !defined(TARGET_ARCH_DBC) + const Function& signature = Function::ZoneHandle(Z, function.FfiCSignature()); + const auto& arg_reps = *compiler::ffi::ArgumentRepresentations(signature); + const auto& arg_locs = *compiler::ffi::ArgumentLocations(arg_reps); + const auto& callback_locs = + *compiler::ffi::CallbackArgumentTranslator::TranslateArgumentLocations( + arg_locs); + + graph_entry_ = + new (Z) GraphEntryInstr(*parsed_function_, Compiler::kNoOSRDeoptId); + + auto* const native_entry = new (Z) NativeEntryInstr( + &arg_locs, graph_entry_, AllocateBlockId(), CurrentTryIndex(), + GetNextDeoptId(), function.FfiCallbackId()); + + graph_entry_->set_normal_entry(native_entry); + + Fragment function_body(native_entry); + function_body += CheckStackOverflowInPrologue(function.token_pos()); + + // Wrap the entire method in a big try/catch. This is important to ensure that + // the VM does not crash if the callback throws an exception. + const intptr_t try_handler_index = AllocateTryIndex(); + Fragment body = TryCatch(try_handler_index); + ++try_depth_; + + // Box and push the arguments. + AbstractType& ffi_type = AbstractType::Handle(Z); + for (intptr_t i = 0, n = callback_locs.length(); i < n; ++i) { + ffi_type = signature.ParameterTypeAt(i + 1); + auto* parameter = + new (Z) NativeParameterInstr(callback_locs[i], arg_reps[i]); + Push(parameter); + body <<= parameter; + body += FfiConvertArgumentToDart(ffi_type, arg_reps[i]); + body += PushArgument(); + } + + // Call the target. + // + // TODO(36748): Determine the hot-reload semantics of callbacks and update the + // rebind-rule accordingly. + body += StaticCall(TokenPosition::kNoSource, + Function::ZoneHandle(Z, function.FfiCallbackTarget()), + callback_locs.length(), Array::empty_array(), + ICData::kNoRebind); + + ffi_type = signature.result_type(); + const Representation result_rep = + compiler::ffi::ResultRepresentation(signature); + body += FfiConvertArgumentToNative(function, ffi_type, result_rep); + body += NativeReturn(result_rep); + + --try_depth_; + function_body += body; + + ++catch_depth_; + Fragment catch_body = + CatchBlockEntry(Array::empty_array(), try_handler_index, + /*needs_stacktrace=*/true, /*is_synthesized=*/true); + + catch_body += LoadLocal(CurrentException()); + catch_body += PushArgument(); + catch_body += LoadLocal(CurrentStackTrace()); + catch_body += PushArgument(); + + // Find '_handleExposedException(e, st)' from ffi_patch.dart and call it. + const Library& ffi_lib = + Library::Handle(Z, Library::LookupLibrary(thread_, Symbols::DartFfi())); + const Function& handler = Function::ZoneHandle( + Z, ffi_lib.LookupFunctionAllowPrivate(Symbols::HandleExposedException())); + ASSERT(!handler.IsNull()); + catch_body += StaticCall(TokenPosition::kNoSource, handler, /*num_args=*/2, + /*arg_names=*/Array::empty_array(), ICData::kStatic); + catch_body += Drop(); + + catch_body += FfiExceptionalReturnValue(ffi_type, result_rep); + catch_body += NativeReturn(result_rep); + --catch_depth_; + + PrologueInfo prologue_info(-1, -1); + return new (Z) FlowGraph(*parsed_function_, graph_entry_, last_used_block_id_, + prologue_info); +#else + UNREACHABLE(); +#endif +} + void FlowGraphBuilder::SetCurrentTryCatchBlock(TryCatchBlock* try_catch_block) { try_catch_block_ = try_catch_block; SetCurrentTryIndex(try_catch_block == nullptr ? kInvalidTryIndex diff --git a/runtime/vm/compiler/frontend/kernel_to_il.h b/runtime/vm/compiler/frontend/kernel_to_il.h index 1e34e132e4e..9ccc1a94f51 100644 --- a/runtime/vm/compiler/frontend/kernel_to_il.h +++ b/runtime/vm/compiler/frontend/kernel_to_il.h @@ -101,6 +101,8 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { FlowGraph* BuildGraphOfNoSuchMethodDispatcher(const Function& function); FlowGraph* BuildGraphOfInvokeFieldDispatcher(const Function& function); FlowGraph* BuildGraphOfFfiTrampoline(const Function& function); + FlowGraph* BuildGraphOfFfiCallback(const Function& function); + FlowGraph* BuildGraphOfFfiNative(const Function& function); Fragment NativeFunctionBody(const Function& function, LocalVariable* first_parameter); @@ -236,6 +238,26 @@ class FlowGraphBuilder : public BaseFlowGraphBuilder { // the pointer. Fragment FfiPointerFromAddress(const Type& result_type); + // Pushes an (unboxed) bogus value returned when a native -> Dart callback + // throws an exception. + Fragment FfiExceptionalReturnValue(const AbstractType& result_type, + const Representation target); + + // Pops a Dart object and push the unboxed native version, according to the + // semantics of FFI argument translation. + Fragment FfiConvertArgumentToNative( + const Function& function, + const AbstractType& ffi_type, + const Representation native_representation); + + // Reverse of 'FfiConvertArgumentToNative'. + Fragment FfiConvertArgumentToDart(const AbstractType& ffi_type, + const Representation native_representation); + + // Return from a native -> Dart callback. Can only be used in conjunction with + // NativeEntry and NativeParameter are used. + Fragment NativeReturn(Representation result); + // Bit-wise cast between representations. // Pops the input and pushes the converted result. // Currently only works with equal sizes and floating point <-> integer. diff --git a/runtime/vm/compiler/frontend/scope_builder.cc b/runtime/vm/compiler/frontend/scope_builder.cc index e043d3cc90d..942c77732af 100644 --- a/runtime/vm/compiler/frontend/scope_builder.cc +++ b/runtime/vm/compiler/frontend/scope_builder.cc @@ -389,6 +389,17 @@ ScopeBuildingResult* ScopeBuilder::BuildScopes() { : Object::dynamic_type().raw())); scope_->InsertParameterAt(i, variable); } + // Callbacks need try/catch variables. + if (function.IsFfiTrampoline() && + function.FfiCallbackTarget() != Function::null()) { + ++depth_.try_; + AddTryVariables(); + --depth_.try_; + ++depth_.catch_; + AddCatchVariables(); + FinalizeCatchVariables(); + --depth_.catch_; + } break; case RawFunction::kSignatureFunction: case RawFunction::kIrregexpFunction: diff --git a/runtime/vm/compiler/runtime_api.cc b/runtime/vm/compiler/runtime_api.cc index 758e0658e7f..5c9e8528cd2 100644 --- a/runtime/vm/compiler/runtime_api.cc +++ b/runtime/vm/compiler/runtime_api.cc @@ -500,6 +500,7 @@ word Array::header_size() { V(Thread, top_resource_offset) \ V(Thread, vm_tag_offset) \ V(Thread, safepoint_state_offset) \ + V(Thread, callback_code_offset) \ V(TimelineStream, enabled_offset) \ V(TwoByteString, data_offset) \ V(Type, arguments_offset) \ @@ -582,6 +583,10 @@ word Thread::write_barrier_entry_point_offset() { word Thread::array_write_barrier_entry_point_offset() { return dart::Thread::array_write_barrier_entry_point_offset(); } + +word Thread::verify_callback_isolate_entry_point_offset() { + return dart::Thread::verify_callback_entry_offset(); +} #endif // !defined(TARGET_ARCH_DBC) #if defined(TARGET_ARCH_ARM) || defined(TARGET_ARCH_ARM64) || \ diff --git a/runtime/vm/compiler/runtime_api.h b/runtime/vm/compiler/runtime_api.h index 29ff5ba7ec0..24ce3015fa7 100644 --- a/runtime/vm/compiler/runtime_api.h +++ b/runtime/vm/compiler/runtime_api.h @@ -572,6 +572,7 @@ class Thread : public AllStatic { static word write_barrier_wrappers_thread_offset(intptr_t regno); static word array_write_barrier_entry_point_offset(); static word write_barrier_entry_point_offset(); + static word verify_callback_isolate_entry_point_offset(); static word vm_tag_offset(); static uword vm_tag_compiled_id(); @@ -583,6 +584,8 @@ class Thread : public AllStatic { static uword native_execution_state(); static uword generated_execution_state(); + static word callback_code_offset(); + #if !defined(TARGET_ARCH_DBC) static word write_barrier_code_offset(); static word array_write_barrier_code_offset(); diff --git a/runtime/vm/compiler/stub_code_compiler_arm.cc b/runtime/vm/compiler/stub_code_compiler_arm.cc index 4f23b000b03..07ef14790ed 100644 --- a/runtime/vm/compiler/stub_code_compiler_arm.cc +++ b/runtime/vm/compiler/stub_code_compiler_arm.cc @@ -300,6 +300,10 @@ void StubCodeCompiler::GenerateExitSafepointStub(Assembler* assembler) { __ Ret(); } +void StubCodeCompiler::GenerateVerifyCallbackStub(Assembler* assembler) { + __ Breakpoint(); +} + void StubCodeCompiler::GenerateNullErrorSharedWithoutFPURegsStub( Assembler* assembler) { GenerateSharedStub( diff --git a/runtime/vm/compiler/stub_code_compiler_arm64.cc b/runtime/vm/compiler/stub_code_compiler_arm64.cc index 59cb9978e55..780b7f4f18a 100644 --- a/runtime/vm/compiler/stub_code_compiler_arm64.cc +++ b/runtime/vm/compiler/stub_code_compiler_arm64.cc @@ -239,6 +239,10 @@ void StubCodeCompiler::GenerateExitSafepointStub(Assembler* assembler) { __ Ret(); } +void StubCodeCompiler::GenerateVerifyCallbackStub(Assembler* assembler) { + __ Breakpoint(); +} + // R1: The extracted method. // R4: The type_arguments_field_offset (or 0) void StubCodeCompiler::GenerateBuildMethodExtractorStub( diff --git a/runtime/vm/compiler/stub_code_compiler_ia32.cc b/runtime/vm/compiler/stub_code_compiler_ia32.cc index 1cae9f228e1..62fbb2e88aa 100644 --- a/runtime/vm/compiler/stub_code_compiler_ia32.cc +++ b/runtime/vm/compiler/stub_code_compiler_ia32.cc @@ -164,6 +164,10 @@ void StubCodeCompiler::GenerateExitSafepointStub(Assembler* assembler) { __ ret(); } +void StubCodeCompiler::GenerateVerifyCallbackStub(Assembler* assembler) { + __ Breakpoint(); +} + void StubCodeCompiler::GenerateNullErrorSharedWithoutFPURegsStub( Assembler* assembler) { __ Breakpoint(); diff --git a/runtime/vm/compiler/stub_code_compiler_x64.cc b/runtime/vm/compiler/stub_code_compiler_x64.cc index b2d7da19c4c..92836b2a087 100644 --- a/runtime/vm/compiler/stub_code_compiler_x64.cc +++ b/runtime/vm/compiler/stub_code_compiler_x64.cc @@ -231,6 +231,20 @@ void StubCodeCompiler::GenerateExitSafepointStub(Assembler* assembler) { __ ret(); } +void StubCodeCompiler::GenerateVerifyCallbackStub(Assembler* assembler) { + // SP points to return address, which needs to be the second argument to + // VerifyCallbackIsolate. + __ movq(CallingConventions::kArg2Reg, Address(SPREG, 0)); + + __ EnterFrame(0); + __ ReserveAlignedFrameSpace(0); + __ movq(RAX, + Address(THR, kVerifyCallbackIsolateRuntimeEntry.OffsetFromThread())); + __ CallCFunction(RAX); + __ LeaveFrame(); + __ ret(); +} + // RBX: The extracted method. // RDX: The type_arguments_field_offset (or 0) void StubCodeCompiler::GenerateBuildMethodExtractorStub( @@ -1096,19 +1110,8 @@ void StubCodeCompiler::GenerateInvokeDartCodeStub(Assembler* assembler) { __ pushq(RAX); // The constant target::frame_layout.exit_link_slot_from_entry_fp must be kept - // in sync with the code below. -#if defined(DEBUG) - { - Label ok; - __ leaq(RAX, - Address(RBP, target::frame_layout.exit_link_slot_from_entry_fp * - target::kWordSize)); - __ cmpq(RAX, RSP); - __ j(EQUAL, &ok); - __ Stop("target::frame_layout.exit_link_slot_from_entry_fp mismatch"); - __ Bind(&ok); - } -#endif + // in sync with the code above. + __ EmitEntryFrameVerification(); __ movq(Address(THR, target::Thread::top_exit_frame_info_offset()), Immediate(0)); diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc index 73a41f0245d..79361bab0fd 100644 --- a/runtime/vm/exceptions.cc +++ b/runtime/vm/exceptions.cc @@ -995,6 +995,12 @@ void Exceptions::ThrowRangeError(const char* argument_name, Exceptions::ThrowByType(Exceptions::kRange, args); } +void Exceptions::ThrowUnsupportedError(const char* msg) { + const Array& args = Array::Handle(Array::New(1)); + args.SetAt(0, String::Handle(String::New(msg))); + Exceptions::ThrowByType(Exceptions::kUnsupported, args); +} + void Exceptions::ThrowRangeErrorMsg(const char* msg) { const Array& args = Array::Handle(Array::New(1)); args.SetAt(0, String::Handle(String::New(msg))); diff --git a/runtime/vm/exceptions.h b/runtime/vm/exceptions.h index 894f3741cd6..46b4d68b8ac 100644 --- a/runtime/vm/exceptions.h +++ b/runtime/vm/exceptions.h @@ -84,6 +84,7 @@ class Exceptions : AllStatic { intptr_t expected_from, intptr_t expected_to); DART_NORETURN static void ThrowRangeErrorMsg(const char* msg); + DART_NORETURN static void ThrowUnsupportedError(const char* msg); DART_NORETURN static void ThrowCompileTimeError(const LanguageError& error); // Returns a RawInstance if the exception is successfully created, diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc index 6523ccb38db..2cac15fba40 100644 --- a/runtime/vm/object.cc +++ b/runtime/vm/object.cc @@ -6062,6 +6062,34 @@ RawFunction* Function::FfiCSignature() const { return FfiTrampolineData::Cast(obj).c_signature(); } +int32_t Function::FfiCallbackId() const { + ASSERT(IsFfiTrampoline()); + const Object& obj = Object::Handle(raw_ptr()->data_); + ASSERT(!obj.IsNull()); + return FfiTrampolineData::Cast(obj).callback_id(); +} + +void Function::SetFfiCallbackId(int32_t value) const { + ASSERT(IsFfiTrampoline()); + const Object& obj = Object::Handle(raw_ptr()->data_); + ASSERT(!obj.IsNull()); + FfiTrampolineData::Cast(obj).set_callback_id(value); +} + +RawFunction* Function::FfiCallbackTarget() const { + ASSERT(IsFfiTrampoline()); + const Object& obj = Object::Handle(raw_ptr()->data_); + ASSERT(!obj.IsNull()); + return FfiTrampolineData::Cast(obj).callback_target(); +} + +void Function::SetFfiCallbackTarget(const Function& target) const { + ASSERT(IsFfiTrampoline()); + const Object& obj = Object::Handle(raw_ptr()->data_); + ASSERT(!obj.IsNull()); + FfiTrampolineData::Cast(obj).set_callback_target(target); +} + RawType* Function::SignatureType() const { Type& type = Type::Handle(ExistingSignatureType()); if (type.IsNull()) { @@ -7784,7 +7812,8 @@ bool Function::HasOptimizedCode() const { bool Function::ShouldCompilerOptimize() const { return !FLAG_enable_interpreter || - ((unoptimized_code() != Object::null()) && WasCompiled()); + ((unoptimized_code() != Object::null()) && WasCompiled()) || + ForceOptimize(); } RawString* Function::UserVisibleName() const { @@ -8218,12 +8247,22 @@ void FfiTrampolineData::set_c_signature(const Function& value) const { StorePointer(&raw_ptr()->c_signature_, value.raw()); } +void FfiTrampolineData::set_callback_target(const Function& value) const { + StorePointer(&raw_ptr()->callback_target_, value.raw()); +} + +void FfiTrampolineData::set_callback_id(int32_t callback_id) const { + StoreNonPointer(&raw_ptr()->callback_id_, callback_id); +} + RawFfiTrampolineData* FfiTrampolineData::New() { ASSERT(Object::ffi_trampoline_data_class() != Class::null()); RawObject* raw = Object::Allocate(FfiTrampolineData::kClassId, FfiTrampolineData::InstanceSize(), Heap::kOld); - return reinterpret_cast(raw); + RawFfiTrampolineData* data = reinterpret_cast(raw); + data->ptr()->callback_id_ = -1; + return data; } const char* FfiTrampolineData::ToCString() const { diff --git a/runtime/vm/object.h b/runtime/vm/object.h index 9e7b6c222d6..63fed520c93 100644 --- a/runtime/vm/object.h +++ b/runtime/vm/object.h @@ -1999,6 +1999,20 @@ class Function : public Object { // Can only be used on FFI trampolines. RawFunction* FfiCSignature() const; + // Can only be called on FFI trampolines. + // -1 for Dart -> native calls. + int32_t FfiCallbackId() const; + + // Can only be called on FFI trampolines. + void SetFfiCallbackId(int32_t value) const; + + // Can only be called on FFI trampolines. + // Null for Dart -> native calls. + RawFunction* FfiCallbackTarget() const; + + // Can only be called on FFI trampolines. + void SetFfiCallbackTarget(const Function& target) const; + // Return a new function with instantiated result and parameter types. RawFunction* InstantiateSignatureFrom( const TypeArguments& instantiator_type_arguments, @@ -3130,6 +3144,12 @@ class FfiTrampolineData : public Object { RawFunction* c_signature() const { return raw_ptr()->c_signature_; } void set_c_signature(const Function& value) const; + RawFunction* callback_target() const { return raw_ptr()->callback_target_; } + void set_callback_target(const Function& value) const; + + int32_t callback_id() const { return raw_ptr()->callback_id_; } + void set_callback_id(int32_t value) const; + static RawFfiTrampolineData* New(); FINAL_HEAP_OBJECT_IMPLEMENTATION(FfiTrampolineData, Object); @@ -5000,7 +5020,7 @@ class Code : public Object { return ContainsInstructionAt(raw(), addr); } - static bool ContainsInstructionAt(RawCode* code, uword addr) { + static bool ContainsInstructionAt(const RawCode* code, uword addr) { return Instructions::ContainsPc(code->ptr()->instructions_, addr); } @@ -8266,6 +8286,14 @@ class GrowableObjectArray : public Instance { static RawGrowableObjectArray* New(const Array& array, Heap::Space space = Heap::kNew); + static RawSmi* NoSafepointLength(const RawGrowableObjectArray* array) { + return array->ptr()->length_; + } + + static RawArray* NoSafepointData(const RawGrowableObjectArray* array) { + return array->ptr()->data_; + } + private: RawArray* DataArray() const { return data()->ptr(); } RawObject** ObjectAddr(intptr_t index) const { diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h index 2325fa23e43..9d80fdf6457 100644 --- a/runtime/vm/raw_object.h +++ b/runtime/vm/raw_object.h @@ -1013,7 +1013,19 @@ class RawFfiTrampolineData : public RawObject { VISIT_FROM(RawObject*, signature_type_); RawType* signature_type_; RawFunction* c_signature_; - VISIT_TO(RawObject*, c_signature_); + + // Target Dart method for callbacks, otherwise null. + RawFunction* callback_target_; + + VISIT_TO(RawObject*, callback_target_); + + // Callback id for callbacks, otherwise 0. + // + // The callbacks ids are used so that native callbacks can lookup their own + // code objects, since native code doesn't pass code objects into function + // calls. The callback id is also used to for verifying that callbacks are + // called on the correct isolate. See DLRT_VerifyCallbackIsolate for details. + uint32_t callback_id_; }; class RawField : public RawObject { diff --git a/runtime/vm/runtime_entry.cc b/runtime/vm/runtime_entry.cc index 1a7ba2e9eab..b4b4563baef 100644 --- a/runtime/vm/runtime_entry.cc +++ b/runtime/vm/runtime_entry.cc @@ -26,6 +26,7 @@ #include "vm/service_isolate.h" #include "vm/stack_frame.h" #include "vm/symbols.h" +#include "vm/thread.h" #include "vm/thread_registry.h" #include "vm/type_testing_stubs.h" @@ -2765,4 +2766,29 @@ extern "C" void DFLRT_ExitSafepoint(NativeArguments __unusable_) { } DEFINE_RAW_LEAF_RUNTIME_ENTRY(ExitSafepoint, 0, false, &DFLRT_ExitSafepoint); +// Not registered as a runtime entry because we can't use Thread to look it up. +extern "C" Thread* DLRT_GetThreadForNativeCallback() { + Thread* const thread = Thread::Current(); + if (thread == nullptr) { + FATAL("Cannot invoke native callback outside an isolate."); + } + if (thread->no_callback_scope_depth() != 0) { + FATAL("Cannot invoke native callback when API callbacks are prohibited."); + } + if (!thread->IsMutatorThread()) { + FATAL("Native callbacks must be invoked on the mutator thread."); + } + return thread; +} + +extern "C" void DLRT_VerifyCallbackIsolate(int32_t callback_id, + uword return_address) { + Thread::Current()->VerifyCallbackIsolate(callback_id, return_address); +} +DEFINE_RAW_LEAF_RUNTIME_ENTRY( + VerifyCallbackIsolate, + 1, + false /* is_float */, + reinterpret_cast(&DLRT_VerifyCallbackIsolate)); + } // namespace dart diff --git a/runtime/vm/runtime_entry.h b/runtime/vm/runtime_entry.h index 85e9d7f2262..f072c149975 100644 --- a/runtime/vm/runtime_entry.h +++ b/runtime/vm/runtime_entry.h @@ -145,6 +145,9 @@ class RuntimeEntry : public BaseRuntimeEntry { RUNTIME_ENTRY_LIST(DECLARE_RUNTIME_ENTRY) LEAF_RUNTIME_ENTRY_LIST(DECLARE_LEAF_RUNTIME_ENTRY) +// Expected to be called inside a safepoint. +extern "C" Thread* DLRT_GetThreadForNativeCallback(); + const char* DeoptReasonToCString(ICData::DeoptReasonId deopt_reason); void DeoptimizeAt(const Code& optimized_code, StackFrame* frame); diff --git a/runtime/vm/runtime_entry_list.h b/runtime/vm/runtime_entry_list.h index dd4159d851e..b0c241eec44 100644 --- a/runtime/vm/runtime_entry_list.h +++ b/runtime/vm/runtime_entry_list.h @@ -84,7 +84,8 @@ namespace dart { V(RawBool*, CaseInsensitiveCompareUTF16, RawString*, RawSmi*, RawSmi*, \ RawSmi*) \ V(void, EnterSafepoint) \ - V(void, ExitSafepoint) + V(void, ExitSafepoint) \ + V(void, VerifyCallbackIsolate, int32_t, uword) } // namespace dart diff --git a/runtime/vm/stack_frame.h b/runtime/vm/stack_frame.h index bf67520e7de..3b590052da2 100644 --- a/runtime/vm/stack_frame.h +++ b/runtime/vm/stack_frame.h @@ -471,6 +471,13 @@ DART_FORCE_INLINE static uword LocalVarAddress(uword fp, intptr_t index) { return fp + LocalVarIndex(0, index) * kWordSize; } +#if !defined(TARGET_ARCH_X64) +// For FFI native -> Dart callbacks, the number of stack slots between arguments +// passed on stack and arguments saved in callback prologue. This placeholder +// here is for unsupported architectures. +constexpr intptr_t kCallbackSlotsBeforeSavedArguments = -1; +#endif + } // namespace dart #endif // RUNTIME_VM_STACK_FRAME_H_ diff --git a/runtime/vm/stack_frame_x64.h b/runtime/vm/stack_frame_x64.h index 00d0059f668..193db62e3f1 100644 --- a/runtime/vm/stack_frame_x64.h +++ b/runtime/vm/stack_frame_x64.h @@ -5,6 +5,8 @@ #ifndef RUNTIME_VM_STACK_FRAME_X64_H_ #define RUNTIME_VM_STACK_FRAME_X64_H_ +#include "vm/constants_x64.h" + namespace dart { /* X64 Dart Frame Layout @@ -52,6 +54,13 @@ static const int kExitLinkSlotFromEntryFp = -32; static const int kExitLinkSlotFromEntryFp = -10; #endif // defined(_WIN64) +// For FFI native -> Dart callbacks, the number of stack slots between arguments +// passed on stack and arguments saved in callback prologue. 2 = return adddress +// (1) + saved frame pointer (1). Also add slots for the shadow space, if +// present. +constexpr intptr_t kCallbackSlotsBeforeSavedArguments = + 2 + CallingConventions::kShadowSpaceBytes / kWordSize; + } // namespace dart #endif // RUNTIME_VM_STACK_FRAME_X64_H_ diff --git a/runtime/vm/stub_code_list.h b/runtime/vm/stub_code_list.h index 7f93f5b83c2..248c455cf68 100644 --- a/runtime/vm/stub_code_list.h +++ b/runtime/vm/stub_code_list.h @@ -77,7 +77,8 @@ namespace dart { V(OneArgCheckInlineCacheWithExactnessCheck) \ V(OneArgOptimizedCheckInlineCacheWithExactnessCheck) \ V(EnterSafepoint) \ - V(ExitSafepoint) + V(ExitSafepoint) \ + V(VerifyCallback) #else #define VM_STUB_CODE_LIST(V) \ diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h index 845d780acdd..f265e3e991a 100644 --- a/runtime/vm/symbols.h +++ b/runtime/vm/symbols.h @@ -179,6 +179,7 @@ class ObjectPointerVisitor; V(GetterPrefix, "get:") \ V(GreaterEqualOperator, ">=") \ V(GrowRegExpStack, "_growRegExpStack") \ + V(HandleExposedException, "_handleExposedException") \ V(HaveSameRuntimeType, "_haveSameRuntimeType") \ V(Hide, "hide") \ V(ICData, "ICData") \ diff --git a/runtime/vm/thread.cc b/runtime/vm/thread.cc index 6b48726a76e..71a9b2d3825 100644 --- a/runtime/vm/thread.cc +++ b/runtime/vm/thread.cc @@ -6,6 +6,7 @@ #include "vm/dart_api_state.h" #include "vm/growable_array.h" +#include "vm/heap/safepoint.h" #include "vm/isolate.h" #include "vm/json_stream.h" #include "vm/lockers.h" @@ -76,6 +77,7 @@ Thread::Thread(Isolate* isolate) resume_pc_(0), execution_state_(kThreadInNative), safepoint_state_(0), + ffi_callback_code_(GrowableObjectArray::null()), task_kind_(kUnknownTask), dart_stream_(NULL), thread_lock_(), @@ -668,6 +670,7 @@ void Thread::VisitObjectPointers(ObjectPointerVisitor* visitor, visitor->VisitPointer(reinterpret_cast(&active_stacktrace_)); visitor->VisitPointer(reinterpret_cast(&sticky_error_)); visitor->VisitPointer(reinterpret_cast(&async_stack_trace_)); + visitor->VisitPointer(reinterpret_cast(&ffi_callback_code_)); #if !defined(DART_PRECOMPILED_RUNTIME) if (interpreter() != NULL) { @@ -936,4 +939,45 @@ DisableThreadInterruptsScope::~DisableThreadInterruptsScope() { } } +const intptr_t kInitialCallbackIdsReserved = 1024; +int32_t Thread::AllocateFfiCallbackId() { + Zone* Z = isolate()->current_zone(); + if (ffi_callback_code_ == GrowableObjectArray::null()) { + ffi_callback_code_ = GrowableObjectArray::New(kInitialCallbackIdsReserved); + } + const auto& array = GrowableObjectArray::Handle(Z, ffi_callback_code_); + array.Add(Code::Handle(Z, Code::null())); + return array.Length() - 1; +} + +void Thread::SetFfiCallbackCode(int32_t callback_id, const Code& code) { + Zone* Z = isolate()->current_zone(); + const auto& array = GrowableObjectArray::Handle(Z, ffi_callback_code_); + array.SetAt(callback_id, code); +} + +void Thread::VerifyCallbackIsolate(int32_t callback_id, uword entry) { + NoSafepointScope _; + + const RawGrowableObjectArray* const array = ffi_callback_code_; + if (array == GrowableObjectArray::null()) { + FATAL("Cannot invoke callback on incorrect isolate."); + } + + const RawSmi* const length_smi = + GrowableObjectArray::NoSafepointLength(array); + const intptr_t length = Smi::Value(length_smi); + + if (callback_id < 0 || callback_id >= length) { + FATAL("Cannot invoke callback on incorrect isolate."); + } + + RawObject** const code_array = + Array::DataOf(GrowableObjectArray::NoSafepointData(array)); + const RawCode* const code = Code::RawCast(code_array[callback_id]); + if (!Code::ContainsInstructionAt(code, entry)) { + FATAL("Cannot invoke callback on incorrect isolate."); + } +} + } // namespace dart diff --git a/runtime/vm/thread.h b/runtime/vm/thread.h index c9d81bb2d18..44bb9e61106 100644 --- a/runtime/vm/thread.h +++ b/runtime/vm/thread.h @@ -168,8 +168,8 @@ class Zone; V(uword, monomorphic_miss_entry_, StubCode::MonomorphicMiss().EntryPoint(), \ 0) \ V(uword, optimize_entry_, StubCode::OptimizeFunction().EntryPoint(), 0) \ - V(uword, deoptimize_entry_, StubCode::Deoptimize().EntryPoint(), 0) - + V(uword, deoptimize_entry_, StubCode::Deoptimize().EntryPoint(), 0) \ + V(uword, verify_callback_entry_, StubCode::VerifyCallback().EntryPoint(), 0) #endif #define CACHED_ADDRESSES_LIST(V) \ @@ -315,6 +315,10 @@ class Thread : public ThreadState { return OFFSET_OF(Thread, safepoint_state_); } + static intptr_t callback_code_offset() { + return OFFSET_OF(Thread, ffi_callback_code_); + } + TaskKind task_kind() const { return task_kind_; } // Retrieves and clears the stack overflow flags. These are set by @@ -768,6 +772,13 @@ class Thread : public ThreadState { } } + int32_t AllocateFfiCallbackId(); + void SetFfiCallbackCode(int32_t callback_id, const Code& code); + + // Ensure that 'entry' points within the code of the callback identified by + // 'callback_id'. Aborts otherwise. + void VerifyCallbackIsolate(int32_t callback_id, uword entry); + Thread* next() const { return next_; } // Visit all object pointers. @@ -861,6 +872,7 @@ class Thread : public ThreadState { uword resume_pc_; uword execution_state_; uword safepoint_state_; + RawGrowableObjectArray* ffi_callback_code_; // ---- End accessed from generated code. ---- diff --git a/tests/ffi/ffi.status b/tests/ffi/ffi.status index 2b170cfeeda..024505c22f5 100644 --- a/tests/ffi/ffi.status +++ b/tests/ffi/ffi.status @@ -25,6 +25,9 @@ negative_function_test: Skip [ $arch == x64 || $arch == arm64 || $arch == simdbc64 ] enable_structs_test: SkipByDesign # Tests that structs don't work on 32-bit systems. +[ $arch != x64 ] +function_callbacks_test: Skip # Issue 35761 + [ $runtime == dart_precompiled ] *: Skip # AOT is not yet supported: dartbug.com/35765 diff --git a/tests/ffi/function_callbacks_test.dart b/tests/ffi/function_callbacks_test.dart index 0ad50a93245..bef1599f9da 100644 --- a/tests/ffi/function_callbacks_test.dart +++ b/tests/ffi/function_callbacks_test.dart @@ -8,71 +8,192 @@ library FfiTest; -import 'dart:ffi' as ffi; - +import 'dart:io'; +import 'dart:ffi'; +import 'dart:isolate'; import 'dylib_utils.dart'; import "package:expect/expect.dart"; -import 'coordinate.dart'; +typedef NativeCallbackTest = Int32 Function(Pointer); +typedef NativeCallbackTestFn = int Function(Pointer); -typedef NativeCoordinateOp = Coordinate Function(Coordinate); +final DynamicLibrary testLibrary = dlopenPlatformSpecific("ffi_test_functions"); -typedef CoordinateTrice = Coordinate Function( - ffi.Pointer>, Coordinate); +class Test { + final String name; + final Pointer callback; + final bool skip; -void main() { - testFunctionWithFunctionPointer(); - testNativeFunctionWithFunctionPointer(); + Test(this.name, this.callback, {bool skipIf: false}) : skip = skipIf {} + + void run() { + if (skip) return; + + final NativeCallbackTestFn tester = testLibrary + .lookupFunction("Test$name"); + final int testCode = tester(callback); + if (testCode != 0) { + Expect.fail("Test $name failed."); + } + } } -ffi.DynamicLibrary ffiTestFunctions = - dlopenPlatformSpecific("ffi_test_functions"); +typedef SimpleAdditionType = Int32 Function(Int32, Int32); +int simpleAddition(int x, int y) => x + y; -/// pass a pointer to a c function as an argument to a c function -void testFunctionWithFunctionPointer() { - ffi.Pointer> - transposeCoordinatePointer = - ffiTestFunctions.lookup("TransposeCoordinate"); +typedef IntComputationType = Int64 Function(Int8, Int16, Int32, Int64); +int intComputation(int a, int b, int c, int d) => d - c + b - a; - ffi.Pointer> p2 = - ffiTestFunctions.lookup("CoordinateUnOpTrice"); - CoordinateTrice coordinateUnOpTrice = p2.asFunction(); +typedef UintComputationType = Uint64 Function(Uint8, Uint16, Uint32, Uint64); +int uintComputation(int a, int b, int c, int d) => d - c + b - a; - Coordinate c1 = Coordinate(10.0, 20.0, null); - c1.next = c1; +typedef SimpleMultiplyType = Double Function(Double); +double simpleMultiply(double x) => x * 1.337; - Coordinate result = coordinateUnOpTrice(transposeCoordinatePointer, c1); +typedef SimpleMultiplyFloatType = Float Function(Float); +double simpleMultiplyFloat(double x) => x * 1.337; - print(result.runtimeType); - print(result.x); - print(result.y); - - c1.free(); +typedef ManyIntsType = IntPtr Function(IntPtr, IntPtr, IntPtr, IntPtr, IntPtr, + IntPtr, IntPtr, IntPtr, IntPtr, IntPtr); +int manyInts( + int a, int b, int c, int d, int e, int f, int g, int h, int i, int j) { + return a + b + c + d + e + f + g + h + i + j; } -typedef BinaryOp = int Function(int, int); - -typedef NativeIntptrBinOp = ffi.IntPtr Function(ffi.IntPtr, ffi.IntPtr); - -typedef NativeIntptrBinOpLookup - = ffi.Pointer> Function(); - -void testNativeFunctionWithFunctionPointer() { - ffi.Pointer> p1 = - ffiTestFunctions.lookup("IntptrAdditionClosure"); - NativeIntptrBinOpLookup intptrAdditionClosure = p1.asFunction(); - - ffi.Pointer> intptrAdditionPointer = - intptrAdditionClosure(); - BinaryOp intptrAddition = intptrAdditionPointer.asFunction(); - Expect.equals(37, intptrAddition(10, 27)); +typedef ManyDoublesType = Double Function(Double, Double, Double, Double, + Double, Double, Double, Double, Double, Double); +double manyDoubles(double a, double b, double c, double d, double e, double f, + double g, double h, double i, double j) { + return a + b + c + d + e + f + g + h + i + j; } -int myPlus(int a, int b) => a + b; +typedef ManyArgsType = Double Function( + IntPtr, + Float, + IntPtr, + Double, + IntPtr, + Float, + IntPtr, + Double, + IntPtr, + Float, + IntPtr, + Double, + IntPtr, + Float, + IntPtr, + Double, + IntPtr, + Float, + IntPtr, + Double); +double manyArgs( + int _1, + double _2, + int _3, + double _4, + int _5, + double _6, + int _7, + double _8, + int _9, + double _10, + int _11, + double _12, + int _13, + double _14, + int _15, + double _16, + int _17, + double _18, + int _19, + double _20) { + return _1 + + _2 + + _3 + + _4 + + _5 + + _6 + + _7 + + _8 + + _9 + + _10 + + _11 + + _12 + + _13 + + _14 + + _15 + + _16 + + _17 + + _18 + + _19 + + _20; +} -typedef NativeApplyTo42And74Type = ffi.IntPtr Function( - ffi.Pointer>); +typedef StoreType = Pointer Function(Pointer); +Pointer store(Pointer ptr) => ptr.elementAt(1)..store(1337); -typedef ApplyTo42And74Type = int Function( - ffi.Pointer>); +typedef NullPointersType = Pointer Function(Pointer); +Pointer nullPointers(Pointer ptr) => ptr?.elementAt(1); + +typedef ReturnNullType = Int32 Function(); +int returnNull() { + print('Expect "unhandled exception" error message to follow.'); + return null; +} + +typedef ReturnVoid = Void Function(); +void returnVoid() {} + +final List testcases = [ + Test("SimpleAddition", fromFunction(simpleAddition)), + Test("IntComputation", fromFunction(intComputation)), + Test("UintComputation", fromFunction(uintComputation)), + Test("SimpleMultiply", fromFunction(simpleMultiply)), + Test("SimpleMultiplyFloat", + fromFunction(simpleMultiplyFloat)), + Test("ManyInts", fromFunction(manyInts)), + Test("ManyDoubles", fromFunction(manyDoubles)), + Test("ManyArgs", fromFunction(manyArgs)), + Test("Store", fromFunction(store)), + Test("NullPointers", fromFunction(nullPointers)), + Test("ReturnNull", fromFunction(returnNull)), +]; + +testCallbackWrongThread() => + Test("CallbackWrongThread", fromFunction(returnVoid)).run(); + +testCallbackOutsideIsolate() => + Test("CallbackOutsideIsolate", fromFunction(returnVoid)).run(); + +isolateHelper(int callbackPointer) { + final Pointer ptr = fromAddress(callbackPointer); + final NativeCallbackTestFn tester = + testLibrary.lookupFunction( + "TestCallbackWrongIsolate"); + Expect.equals(0, tester(ptr)); +} + +testCallbackWrongIsolate() async { + final int callbackPointer = fromFunction(returnVoid).address; + final ReceivePort exitPort = ReceivePort(); + await Isolate.spawn(isolateHelper, callbackPointer, + errorsAreFatal: true, onExit: exitPort.sendPort); + await exitPort.first; +} + +void main() async { + testcases.forEach((t) => t.run()); + + // These tests terminate the process after successful completion, so we have + // to run them separately. + // + // Since they use signal handlers they only run on Linux. + if (Platform.isLinux && !const bool.fromEnvironment("dart.vm.product")) { + testCallbackWrongThread(); //# 01: ok + testCallbackOutsideIsolate(); //# 02: ok + await testCallbackWrongIsolate(); //# 03: ok + } +}