// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/compiler/ffi.h" #include "include/dart_api.h" #include "vm/bootstrap_natives.h" #include "vm/class_finalizer.h" #include "vm/compiler/assembler/assembler.h" #include "vm/exceptions.h" #include "vm/log.h" #include "vm/native_arguments.h" #include "vm/native_entry.h" #include "vm/object.h" #include "vm/object_store.h" #include "vm/symbols.h" namespace dart { // The following functions are runtime checks on type arguments. // Some checks are also performed in kernel transformation, these are asserts. // Some checks are only performed at runtime to allow for generic code, these // throw ArgumentExceptions. static void ThrowTypeArgumentError(const AbstractType& type_arg, const char* expected) { const String& error = String::Handle(String::NewFormatted( "Type argument (%s) should be a %s", String::Handle(type_arg.UserVisibleName()).ToCString(), expected)); Exceptions::ThrowArgumentError(error); } static bool IsPointerType(const AbstractType& type) { // Do a fast check for predefined types. classid_t type_cid = type.type_class_id(); if (RawObject::IsFfiPointerClassId(type_cid)) { return true; } // Do a slow check for subtyping. const Class& pointer_class = Class::Handle(Isolate::Current()->object_store()->ffi_pointer_class()); AbstractType& pointer_type = AbstractType::Handle(pointer_class.DeclarationType()); pointer_type ^= pointer_type.InstantiateFrom(Object::null_type_arguments(), Object::null_type_arguments(), kNoneFree, NULL, Heap::kNew); ASSERT(pointer_type.IsInstantiated()); ASSERT(type.IsInstantiated()); return type.IsSubtypeOf(pointer_type, Heap::kNew); } static bool IsConcreteNativeType(const AbstractType& type) { // Do a fast check for predefined types. classid_t type_cid = type.type_class_id(); if (RawObject::IsFfiNativeTypeTypeClassId(type_cid)) { return false; } if (RawObject::IsFfiTypeClassId(type_cid)) { return true; } // Do a slow check for subtyping. const Class& native_type_class = Class::Handle( Isolate::Current()->object_store()->ffi_native_type_class()); AbstractType& native_type_type = AbstractType::Handle(native_type_class.DeclarationType()); return type.IsSubtypeOf(native_type_type, Heap::kNew); } static void CheckIsConcreteNativeType(const AbstractType& type) { if (!IsConcreteNativeType(type)) { ThrowTypeArgumentError(type, "concrete sub type of NativeType"); } } static bool IsNativeFunction(const AbstractType& type_arg) { classid_t type_cid = type_arg.type_class_id(); return RawObject::IsFfiTypeNativeFunctionClassId(type_cid); } static void CheckSized(const AbstractType& type_arg) { classid_t type_cid = type_arg.type_class_id(); if (RawObject::IsFfiTypeVoidClassId(type_cid) || RawObject::IsFfiTypeNativeFunctionClassId(type_cid)) { const String& error = String::Handle(String::NewFormatted( "%s does not have a predefined size (@unsized). " "Unsized NativeTypes do not support [sizeOf] because their size " "is unknown. " "Consequently, [allocate], [Pointer.load], [Pointer.store], and " "[Pointer.elementAt] are not available.", String::Handle(type_arg.UserVisibleName()).ToCString())); Exceptions::ThrowArgumentError(error); } } // Checks that a dart type correspond to a [NativeType]. // Because this is checked already in a kernel transformation, it does not throw // an ArgumentException but a boolean which should be asserted. // // [Int8] -> [int] // [Int16] -> [int] // [Int32] -> [int] // [Int64] -> [int] // [Uint8] -> [int] // [Uint16] -> [int] // [Uint32] -> [int] // [Uint64] -> [int] // [IntPtr] -> [int] // [Double] -> [double] // [Float] -> [double] // [Pointer] -> [Pointer] // T extends [Pointer] -> T // [NativeFunction] S1 Function(S2, S3) // where DartRepresentationOf(Tn) -> Sn static bool DartAndCTypeCorrespond(const AbstractType& native_type, const AbstractType& dart_type) { classid_t native_type_cid = native_type.type_class_id(); if (RawObject::IsFfiTypeIntClassId(native_type_cid)) { return dart_type.IsSubtypeOf(AbstractType::Handle(Type::IntType()), Heap::kNew); } if (RawObject::IsFfiTypeDoubleClassId(native_type_cid)) { return dart_type.IsSubtypeOf(AbstractType::Handle(Type::Double()), Heap::kNew); } if (RawObject::IsFfiPointerClassId(native_type_cid)) { return native_type.Equals(dart_type) || dart_type.IsNullType(); } if (RawObject::IsFfiTypeNativeFunctionClassId(native_type_cid)) { if (!dart_type.IsFunctionType()) { return false; } TypeArguments& nativefunction_type_args = TypeArguments::Handle(native_type.arguments()); AbstractType& nativefunction_type_arg = AbstractType::Handle(nativefunction_type_args.TypeAt(0)); if (!nativefunction_type_arg.IsFunctionType()) { return false; } Function& dart_function = Function::Handle(((Type&)dart_type).signature()); if (dart_function.NumTypeParameters() != 0 || dart_function.HasOptionalPositionalParameters() || dart_function.HasOptionalNamedParameters()) { return false; } Function& nativefunction_function = Function::Handle(((Type&)nativefunction_type_arg).signature()); if (nativefunction_function.NumTypeParameters() != 0 || nativefunction_function.HasOptionalPositionalParameters() || nativefunction_function.HasOptionalNamedParameters()) { return false; } if (!(dart_function.NumParameters() == nativefunction_function.NumParameters())) { return false; } if (!DartAndCTypeCorrespond( AbstractType::Handle(nativefunction_function.result_type()), AbstractType::Handle(dart_function.result_type()))) { return false; } for (intptr_t i = 0; i < dart_function.NumParameters(); i++) { if (!DartAndCTypeCorrespond( AbstractType::Handle(nativefunction_function.ParameterTypeAt(i)), AbstractType::Handle(dart_function.ParameterTypeAt(i)))) { return false; } } } return true; } // The following functions are runtime checks on arguments. // Note that expected_from and expected_to are inclusive. static void CheckRange(const Integer& argument_value, intptr_t expected_from, intptr_t expected_to, const char* argument_name) { int64_t value = argument_value.AsInt64Value(); if (value < expected_from || expected_to < value) { Exceptions::ThrowRangeError(argument_name, argument_value, expected_from, expected_to); } } static const Pointer& AsPointer(const Instance& instance) { if (!instance.IsPointer()) { const String& error = String::Handle(String::NewFormatted( "Expected a Pointer object but found %s", instance.ToCString())); Exceptions::ThrowArgumentError(error); } return Pointer::Cast(instance); } static const Integer& AsInteger(const Instance& instance) { if (!instance.IsInteger()) { const String& error = String::Handle(String::NewFormatted( "Expected an int but found %s", instance.ToCString())); Exceptions::ThrowArgumentError(error); } return Integer::Cast(instance); } static const Double& AsDouble(const Instance& instance) { if (!instance.IsDouble()) { const String& error = String::Handle(String::NewFormatted( "Expected a double but found %s", instance.ToCString())); Exceptions::ThrowArgumentError(error); } return Double::Cast(instance); } // The remainder of this file implements the dart:ffi native methods. DEFINE_NATIVE_ENTRY(Ffi_allocate, 1, 1) { // TODO(dacoharkes): When we have a way of determining the size of structs in // the VM, change the signature so we can allocate structs, subtype of // Pointer. https://github.com/dart-lang/sdk/issues/35782 GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); CheckIsConcreteNativeType(type_arg); CheckSized(type_arg); GET_NON_NULL_NATIVE_ARGUMENT(Integer, argCount, arguments->NativeArgAt(0)); int64_t count = argCount.AsInt64Value(); classid_t type_cid = type_arg.type_class_id(); int64_t max_count = INTPTR_MAX / compiler::ffi::ElementSizeInBytes(type_cid); CheckRange(argCount, 1, max_count, "count"); size_t size = compiler::ffi::ElementSizeInBytes(type_cid) * count; intptr_t memory = reinterpret_cast(malloc(size)); if (memory == 0) { const String& error = String::Handle(String::NewFormatted( "allocating (%" Pd ") bytes of memory failed", size)); Exceptions::ThrowArgumentError(error); } RawPointer* result = Pointer::New(type_arg, Integer::Handle(zone, Integer::New(memory))); return result; } DEFINE_NATIVE_ENTRY(Ffi_fromAddress, 1, 1) { GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); TypeArguments& type_args = TypeArguments::Handle(type_arg.arguments()); AbstractType& native_type = AbstractType::Handle( type_args.TypeAtNullSafe(Pointer::kNativeTypeArgPos)); CheckIsConcreteNativeType(native_type); GET_NON_NULL_NATIVE_ARGUMENT(Integer, arg_ptr, arguments->NativeArgAt(0)); // TODO(dacoharkes): should this return NULL if address is 0? // https://github.com/dart-lang/sdk/issues/35756 RawPointer* result = Pointer::New(native_type, arg_ptr, type_arg.type_class_id()); return result; } DEFINE_NATIVE_ENTRY(Ffi_elementAt, 0, 2) { GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); GET_NON_NULL_NATIVE_ARGUMENT(Integer, index, arguments->NativeArgAt(1)); AbstractType& pointer_type_arg = AbstractType::Handle(zone, pointer.type_argument()); CheckSized(pointer_type_arg); classid_t class_id = pointer_type_arg.type_class_id(); Integer& address = Integer::Handle(zone, pointer.GetCMemoryAddress()); address = Integer::New(address.AsInt64Value() + index.AsInt64Value() * compiler::ffi::ElementSizeInBytes(class_id)); RawPointer* result = Pointer::New(pointer_type_arg, address); return result; } DEFINE_NATIVE_ENTRY(Ffi_offsetBy, 0, 2) { GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); GET_NON_NULL_NATIVE_ARGUMENT(Integer, offset, arguments->NativeArgAt(1)); AbstractType& pointer_type_arg = AbstractType::Handle(pointer.type_argument()); intptr_t address = Integer::Handle(zone, pointer.GetCMemoryAddress()).AsInt64Value() + offset.AsInt64Value(); RawPointer* result = Pointer::New( pointer_type_arg, Integer::Handle(zone, Integer::New(address))); return result; } DEFINE_NATIVE_ENTRY(Ffi_cast, 1, 1) { GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); TypeArguments& type_args = TypeArguments::Handle(type_arg.arguments()); AbstractType& native_type = AbstractType::Handle( type_args.TypeAtNullSafe(Pointer::kNativeTypeArgPos)); CheckIsConcreteNativeType(native_type); const Integer& address = Integer::Handle(zone, pointer.GetCMemoryAddress()); RawPointer* result = Pointer::New(native_type, address, type_arg.type_class_id()); return result; } DEFINE_NATIVE_ENTRY(Ffi_free, 0, 1) { GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); const Integer& address = Integer::Handle(zone, pointer.GetCMemoryAddress()); free(reinterpret_cast(address.AsInt64Value())); pointer.SetCMemoryAddress(Integer::Handle(zone, Integer::New(0))); return Object::null(); } DEFINE_NATIVE_ENTRY(Ffi_address, 0, 1) { GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); return pointer.GetCMemoryAddress(); } static RawInstance* BoxLoadPointer(Zone* zone, uint8_t* address, const AbstractType& instance_type_arg, intptr_t type_cid) { // TODO(dacoharkes): should this return NULL if addres is 0? // https://github.com/dart-lang/sdk/issues/35756 if (address == nullptr) { return Instance::null(); } AbstractType& type_arg = AbstractType::Handle(TypeArguments::Handle(instance_type_arg.arguments()) .TypeAt(Pointer::kNativeTypeArgPos)); return Pointer::New( type_arg, Integer::Handle(zone, Integer::New(reinterpret_cast(address))), type_cid); } static RawInstance* LoadValue(Zone* zone, uint8_t* address, const AbstractType& instance_type_arg) { classid_t type_cid = instance_type_arg.type_class_id(); switch (type_cid) { case kFfiInt8Cid: return Integer::New(*reinterpret_cast(address)); case kFfiInt16Cid: return Integer::New(*reinterpret_cast(address)); case kFfiInt32Cid: return Integer::New(*reinterpret_cast(address)); case kFfiInt64Cid: return Integer::New(*reinterpret_cast(address)); case kFfiUint8Cid: return Integer::NewFromUint64(*reinterpret_cast(address)); case kFfiUint16Cid: return Integer::NewFromUint64(*reinterpret_cast(address)); case kFfiUint32Cid: return Integer::NewFromUint64(*reinterpret_cast(address)); case kFfiUint64Cid: return Integer::NewFromUint64(*reinterpret_cast(address)); case kFfiIntPtrCid: return Integer::New(*reinterpret_cast(address)); case kFfiFloatCid: return Double::New(*reinterpret_cast(address)); case kFfiDoubleCid: return Double::New(*reinterpret_cast(address)); case kFfiPointerCid: default: ASSERT(IsPointerType(instance_type_arg)); return BoxLoadPointer(zone, *reinterpret_cast(address), instance_type_arg, type_cid); } } DEFINE_NATIVE_ENTRY(Ffi_load, 1, 1) { GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); AbstractType& pointer_type_arg = AbstractType::Handle(pointer.type_argument()); CheckSized(pointer_type_arg); ASSERT(DartAndCTypeCorrespond(pointer_type_arg, type_arg)); uint8_t* address = reinterpret_cast( Integer::Handle(pointer.GetCMemoryAddress()).AsInt64Value()); return LoadValue(zone, address, pointer_type_arg); } static void StoreValue(Zone* zone, const Pointer& pointer, classid_t type_cid, const Instance& new_value) { uint8_t* address = reinterpret_cast( Integer::Handle(pointer.GetCMemoryAddress()).AsInt64Value()); AbstractType& pointer_type_arg = AbstractType::Handle(pointer.type_argument()); switch (type_cid) { case kFfiInt8Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiInt16Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiInt32Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiInt64Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiUint8Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiUint16Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiUint32Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiUint64Cid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiIntPtrCid: *reinterpret_cast(address) = AsInteger(new_value).AsInt64Value(); break; case kFfiFloatCid: *reinterpret_cast(address) = AsDouble(new_value).value(); break; case kFfiDoubleCid: *reinterpret_cast(address) = AsDouble(new_value).value(); break; case kFfiPointerCid: default: { ASSERT(IsPointerType(pointer_type_arg)); intptr_t new_value_unwrapped = 0; if (!new_value.IsNull()) { ASSERT(new_value.IsPointer()); new_value_unwrapped = Integer::Handle(AsPointer(new_value).GetCMemoryAddress()) .AsInt64Value(); // TODO(dacoharkes): should this return NULL if addres is 0? // https://github.com/dart-lang/sdk/issues/35756 } *reinterpret_cast(address) = new_value_unwrapped; } break; } } DEFINE_NATIVE_ENTRY(Ffi_store, 0, 2) { GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); GET_NATIVE_ARGUMENT(Instance, new_value, arguments->NativeArgAt(1)); AbstractType& arg_type = AbstractType::Handle(new_value.GetType(Heap::kNew)); AbstractType& pointer_type_arg = AbstractType::Handle(pointer.type_argument()); CheckSized(pointer_type_arg); ASSERT(DartAndCTypeCorrespond(pointer_type_arg, arg_type)); classid_t type_cid = pointer_type_arg.type_class_id(); StoreValue(zone, pointer, type_cid, new_value); return Object::null(); } DEFINE_NATIVE_ENTRY(Ffi_sizeOf, 1, 0) { GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); CheckIsConcreteNativeType(type_arg); CheckSized(type_arg); classid_t type_cid = type_arg.type_class_id(); return Smi::New(compiler::ffi::ElementSizeInBytes(type_cid)); } // TODO(dacoharkes): Cache the trampolines. // We can possibly address simultaniously with 'precaching' in AOT. static RawFunction* TrampolineFunction(const Function& dart_signature, const Function& c_signature) { Thread* thread = Thread::Current(); Zone* zone = thread->zone(); String& name = String::ZoneHandle(Symbols::New(Thread::Current(), "FfiTrampoline")); const Library& lib = Library::Handle(Library::FfiLibrary()); const Class& owner_class = Class::Handle(lib.toplevel_class()); Function& function = Function::Handle(zone, 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); function.set_num_fixed_parameters(dart_signature.num_fixed_parameters()); function.set_result_type(AbstractType::Handle(dart_signature.result_type())); function.set_parameter_types(Array::Handle(dart_signature.parameter_types())); // The signature function won't have any names for the parameters. We need to // assign unique names for scope building and error messages. const intptr_t num_params = dart_signature.num_fixed_parameters(); const Array& parameter_names = Array::Handle(Array::New(num_params)); for (intptr_t i = 0; i < num_params; ++i) { if (i == 0) { name = Symbols::ClosureParameter().raw(); } else { name = Symbols::NewFormatted(thread, ":ffiParam%" Pd, i); } parameter_names.SetAt(i, name); } function.set_parameter_names(parameter_names); function.SetFfiCSignature(c_signature); return function.raw(); } DEFINE_NATIVE_ENTRY(Ffi_asFunction, 1, 1) { GET_NON_NULL_NATIVE_ARGUMENT(Pointer, pointer, arguments->NativeArgAt(0)); AbstractType& pointer_type_arg = AbstractType::Handle(pointer.type_argument()); ASSERT(IsNativeFunction(pointer_type_arg)); GET_NATIVE_TYPE_ARGUMENT(type_arg, arguments->NativeTypeArgAt(0)); ASSERT(DartAndCTypeCorrespond(pointer_type_arg, type_arg)); Function& dart_signature = Function::Handle(Type::Cast(type_arg).signature()); TypeArguments& nativefunction_type_args = TypeArguments::Handle(pointer_type_arg.arguments()); AbstractType& nativefunction_type_arg = AbstractType::Handle(nativefunction_type_args.TypeAt(0)); Function& c_signature = Function::Handle(Type::Cast(nativefunction_type_arg).signature()); Function& function = Function::Handle(TrampolineFunction(dart_signature, c_signature)); // Set the c function pointer in the context of the closure rather than in // the function so that we can reuse the function for each c function with // the same signature. Context& context = Context::Handle(Context::New(1)); context.SetAt(0, Integer::Handle(zone, pointer.GetCMemoryAddress())); RawClosure* raw_closure = Closure::New(Object::null_type_arguments(), Object::null_type_arguments(), function, context, Heap::kOld); return raw_closure; } // Generates assembly to trampoline from C++ back into Dart. static void* GenerateFfiInverseTrampoline(const Function& signature, void* dart_entry_point) { #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(); #else // TODO(dacoharkes): Implement this. // https://github.com/dart-lang/sdk/issues/35761 // Look at StubCode::GenerateInvokeDartCodeStub. UNREACHABLE(); #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()); 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); type_args ^= type_args.Canonicalize(); Class& native_function_class = Class::Handle( Isolate::Current()->class_table()->At(kFfiNativeFunctionCid)); native_function_class.EnsureIsFinalized(Thread::Current()); Type& native_function_type = Type::Handle( Type::New(native_function_class, type_args, TokenPosition::kNoSource)); native_function_type ^= ClassFinalizer::FinalizeType(Class::Handle(), native_function_type); native_function_type ^= native_function_type.Canonicalize(); address = 0; // https://github.com/dart-lang/sdk/issues/35761 Pointer& result = Pointer::Handle(Pointer::New( native_function_type, Integer::Handle(zone, Integer::New(address)))); return result.raw(); } } // namespace dart