blob: 1afc9b53557367e30fef7e77432634148f94949c [file] [log] [blame]
// 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.
// TODO(dacoharkes): Move this into compiler namespace.
#include "vm/class_id.h"
#include "vm/globals.h"
#include "vm/stub_code.h"
#if defined(TARGET_ARCH_X64) && !defined(DART_PRECOMPILED_RUNTIME)
#include "vm/compiler/assembler/assembler.h"
#include "vm/compiler/assembler/disassembler.h"
#include "vm/compiler/backend/flow_graph_compiler.h"
#include "vm/compiler/ffi.h"
#include "vm/compiler/jit/compiler.h"
#include "vm/constants_x64.h"
#include "vm/dart_entry.h"
#include "vm/heap/heap.h"
#include "vm/heap/scavenger.h"
#include "vm/instructions.h"
#include "vm/object_store.h"
#include "vm/resolver.h"
#include "vm/stack_frame.h"
#include "vm/tags.h"
#include "vm/type_testing_stubs.h"
#define __ assembler->
namespace dart {
static intptr_t NumStackArguments(
const ZoneGrowableArray<Location>& locations) {
intptr_t num_arguments = locations.length();
intptr_t num_stack_arguments = 0;
for (intptr_t i = 0; i < num_arguments; i++) {
if (locations.At(i).IsStackSlot()) {
num_stack_arguments++;
}
}
return num_stack_arguments;
}
// Input parameters:
// Register reg : a Null, or something else
static void GenerateNotNullCheck(Assembler* assembler, Register reg) {
Label not_null;
Address throw_null_pointer_address =
Address(THR, Thread::OffsetFromThread(&kArgumentNullErrorRuntimeEntry));
__ CompareObject(reg, Object::null_object());
__ j(NOT_EQUAL, &not_null, Assembler::kNearJump);
// TODO(dacoharkes): Create the message here and use
// kArgumentErrorRuntimeEntry to report which argument was null.
__ movq(CODE_REG, Address(THR, Thread::call_to_runtime_stub_offset()));
__ movq(RBX, throw_null_pointer_address);
__ movq(R10, Immediate(0));
__ call(Address(THR, Thread::call_to_runtime_entry_point_offset()));
__ Bind(&not_null);
}
// Saves an int64 in the thread so GC does not trip.
//
// Input parameters:
// Register src : a C int64
static void GenerateSaveInt64GCSafe(Assembler* assembler, Register src) {
__ movq(Address(THR, Thread::unboxed_int64_runtime_arg_offset()), src);
}
// Loads an int64 from the thread.
static void GenerateLoadInt64GCSafe(Assembler* assembler, Register dst) {
__ movq(dst, Address(THR, Thread::unboxed_int64_runtime_arg_offset()));
}
// Takes a Dart int and converts it to a C int64.
//
// Input parameters:
// Register reg : a Dart Null, Smi, or Mint
// Output parameters:
// Register reg : a C int64
// Invariant: keeps ArgumentRegisters and XmmArgumentRegisters intact
void GenerateMarshalInt64(Assembler* assembler, Register reg) {
ASSERT(reg != TMP);
ASSERT((1 << TMP & CallingConventions::kArgumentRegisters) == 0);
Label done, not_smi;
// Exception on Null
GenerateNotNullCheck(assembler, reg);
// Smi or Mint?
__ movq(TMP, reg);
__ testq(TMP, Immediate(kSmiTagMask));
__ j(NOT_ZERO, &not_smi, Assembler::kNearJump);
// Smi
__ SmiUntag(reg);
__ jmp(&done, Assembler::kNearJump);
// Mint
__ Bind(&not_smi);
__ movq(reg, FieldAddress(reg, Mint::value_offset()));
__ Bind(&done);
}
// Takes a C int64 and converts it to a Dart int.
//
// Input parameters:
// RAX : a C int64
// Output paramaters:
// RAX : a Dart Smi or Mint
static void GenerateUnmarshalInt64(Assembler* assembler) {
const Class& mint_class =
Class::ZoneHandle(Isolate::Current()->object_store()->mint_class());
ASSERT(!mint_class.IsNull());
const auto& mint_allocation_stub =
Code::ZoneHandle(StubCode::GetAllocationStubForClass(mint_class));
ASSERT(!mint_allocation_stub.IsNull());
Label done;
// Try whether it fits in a Smi.
__ movq(TMP, RAX);
__ SmiTag(RAX);
__ j(NO_OVERFLOW, &done, Assembler::kNearJump);
// Mint
// Backup result value (to avoid GC).
GenerateSaveInt64GCSafe(assembler, TMP);
// Allocate object (can call into runtime).
__ Call(mint_allocation_stub);
// Store result value.
GenerateLoadInt64GCSafe(assembler, TMP);
__ movq(FieldAddress(RAX, Mint::value_offset()), TMP);
__ Bind(&done);
}
// Takes a Dart double and converts it into a C double.
//
// Input parameters:
// Register reg : a Dart Null or Double
// Output parameters:
// XmmRegister xmm_reg : a C double
// Invariant: keeps ArgumentRegisters and other XmmArgumentRegisters intact
static void GenerateMarshalDouble(Assembler* assembler,
Register reg,
XmmRegister xmm_reg) {
ASSERT((1 << reg & CallingConventions::kArgumentRegisters) == 0);
// Throw a Dart Exception on Null.
GenerateNotNullCheck(assembler, reg);
__ movq(reg, FieldAddress(reg, Double::value_offset()));
__ movq(xmm_reg, reg);
}
// Takes a C double and converts it into a Dart double.
//
// Input parameters:
// XMM0 : a C double
// Output parameters:
// RAX : a Dart Double
static void GenerateUnmarshalDouble(Assembler* assembler) {
const auto& double_class =
Class::ZoneHandle(Isolate::Current()->object_store()->double_class());
ASSERT(!double_class.IsNull());
const auto& double_allocation_stub =
Code::ZoneHandle(StubCode::GetAllocationStubForClass(double_class));
ASSERT(!double_allocation_stub.IsNull());
// Backup result value (to avoid GC).
__ movq(RAX, XMM0);
GenerateSaveInt64GCSafe(assembler, RAX);
// Allocate object (can call into runtime).
__ Call(double_allocation_stub);
// Store the result value.
GenerateLoadInt64GCSafe(assembler, TMP);
__ movq(FieldAddress(RAX, Double::value_offset()), TMP);
}
// Takes a Dart double and converts into a C float.
//
// Input parameters:
// Register reg : a Dart double
// Output parameters:
// XmmRegister xxmReg : a C float
// Invariant: keeps ArgumentRegisters and other XmmArgumentRegisters intact
static void GenerateMarshalFloat(Assembler* assembler,
Register reg,
XmmRegister xmm_reg) {
ASSERT((1 << reg & CallingConventions::kArgumentRegisters) == 0);
GenerateMarshalDouble(assembler, reg, xmm_reg);
__ cvtsd2ss(xmm_reg, xmm_reg);
}
// Takes a C float and converts it into a Dart double.
//
// Input parameters:
// XMM0 : a C float
// Output paramaters:
// RAX : a Dart Double
static void GenerateUnmarshalFloat(Assembler* assembler) {
__ cvtss2sd(XMM0, XMM0);
GenerateUnmarshalDouble(assembler);
}
// Takes a Dart ffi.Pointer and converts it into a C pointer.
//
// Input parameters:
// Register reg : a Dart ffi.Pointer or Null
// Output parameters:
// Register reg : a C pointer
static void GenerateMarshalPointer(Assembler* assembler, Register reg) {
Label done, not_null;
__ CompareObject(reg, Object::null_object());
__ j(NOT_EQUAL, &not_null, Assembler::kNearJump);
// If null, the address is 0.
__ movq(reg, Immediate(0));
__ jmp(&done);
// If not null but a Pointer, load the address.
__ Bind(&not_null);
__ movq(reg, FieldAddress(reg, Pointer::c_memory_address_offset()));
GenerateMarshalInt64(assembler, reg);
__ Bind(&done);
}
// Takes a C pointer and converts it into a Dart ffi.Pointer or Null.
//
// Input parameters:
// RAX : a C pointer
// Outpot paramaters:
// RAX : a Dart ffi.Pointer or Null
static void GenerateUnmarshalPointer(Assembler* assembler,
Address closure_dart,
const Class& pointer_class) {
Label done, not_null;
ASSERT(!pointer_class.IsNull());
const auto& pointer_allocation_stub =
Code::ZoneHandle(StubCode::GetAllocationStubForClass(pointer_class));
ASSERT(!pointer_allocation_stub.IsNull());
// If the address is 0, return a Dart Null.
__ cmpq(RAX, Immediate(0));
__ j(NOT_EQUAL, &not_null, Assembler::kNearJump);
__ LoadObject(RAX, Object::null_object());
__ jmp(&done);
__ Bind(&not_null);
GenerateUnmarshalInt64(assembler);
__ pushq(RAX);
// Allocate object (can call into runtime).
__ movq(TMP, closure_dart);
__ movq(TMP, FieldAddress(TMP, Closure::function_offset()));
__ movq(TMP, FieldAddress(TMP, Function::result_type_offset()));
__ pushq(FieldAddress(TMP, Type::arguments_offset()));
__ Call(pointer_allocation_stub);
__ popq(TMP); // Pop type arguments.
// Store the result value.
__ popq(RDX);
__ movq(FieldAddress(RAX, Pointer::c_memory_address_offset()), RDX);
__ Bind(&done);
}
static void GenerateMarshalArgument(Assembler* assembler,
const AbstractType& arg_type,
Register reg,
XmmRegister xmm_reg) {
switch (arg_type.type_class_id()) {
case kFfiInt8Cid:
case kFfiInt16Cid:
case kFfiInt32Cid:
case kFfiInt64Cid:
case kFfiUint8Cid:
case kFfiUint16Cid:
case kFfiUint32Cid:
case kFfiUint64Cid:
case kFfiIntPtrCid:
// TODO(dacoharkes): Truncate and sign extend 8 bit and 16 bit, and write
// tests. https://github.com/dart-lang/sdk/issues/35787
GenerateMarshalInt64(assembler, reg);
return;
case kFfiFloatCid:
GenerateMarshalFloat(assembler, reg, xmm_reg);
return;
case kFfiDoubleCid:
GenerateMarshalDouble(assembler, reg, xmm_reg);
return;
case kFfiPointerCid:
default: // Subtypes of Pointer.
GenerateMarshalPointer(assembler, reg);
return;
}
}
static void GenerateUnmarshalResult(Assembler* assembler,
const AbstractType& result_type,
Address closure_dart) {
switch (result_type.type_class_id()) {
case kFfiVoidCid:
__ LoadObject(RAX, Object::null_object());
return;
case kFfiInt8Cid:
case kFfiInt16Cid:
case kFfiInt32Cid:
case kFfiInt64Cid:
case kFfiUint8Cid:
case kFfiUint16Cid:
case kFfiUint32Cid:
case kFfiUint64Cid:
case kFfiIntPtrCid:
GenerateUnmarshalInt64(assembler);
return;
case kFfiFloatCid:
GenerateUnmarshalFloat(assembler);
return;
case kFfiDoubleCid:
GenerateUnmarshalDouble(assembler);
return;
case kFfiPointerCid:
default: // subtypes of Pointer
break;
}
Class& cls = Class::ZoneHandle(Thread::Current()->zone(),
Type::Cast(result_type).type_class());
GenerateUnmarshalPointer(assembler, closure_dart, cls);
}
// Generates a assembly for dart:ffi trampolines:
// - marshal arguments
// - put the arguments in registers and on the c stack
// - invoke the c function
// - (c result register is the same as dart, so keep in place)
// - unmarshal c result
// - return
//
// Input parameters:
// RSP + kWordSize * num_arguments : closure.
// RSP + kWordSize * (num_arguments - 1) : arg 1.
// RSP + kWordSize * (num_arguments - 2) : arg 2.
// RSP + kWordSize : arg n.
// After entering stub:
// RBP = RSP (before stub) - kWordSize
// RBP + kWordSize * (num_arguments + 1) : closure.
// RBP + kWordSize * num_arguments : arg 1.
// RBP + kWordSize * (num_arguments - 1) : arg 2.
// RBP + kWordSize * 2 : arg n.
//
// TODO(dacoharkes): Test truncation on non 64 bits ints and floats.
void GenerateFfiTrampoline(Assembler* assembler, const Function& signature) {
ZoneGrowableArray<Representation>* arg_representations =
ffi::ArgumentRepresentations(signature);
ZoneGrowableArray<Location>* arg_locations =
ffi::ArgumentLocations(*arg_representations);
intptr_t num_dart_arguments = signature.num_fixed_parameters();
intptr_t num_arguments = num_dart_arguments - 1; // ignore closure
__ EnterStubFrame();
// Save exit frame information to enable stack walking as we are about
// to transition to Dart VM C++ code.
__ movq(Address(THR, Thread::top_exit_frame_info_offset()), RBP);
#if defined(DEBUG)
{
Label ok;
// Check that we are always entering from Dart code.
__ movq(TMP, Immediate(VMTag::kDartCompiledTagId));
__ cmpq(TMP, Assembler::VMTagAddress());
__ j(EQUAL, &ok, Assembler::kNearJump);
__ Stop("Not coming from Dart code.");
__ Bind(&ok);
}
#endif
// Reserve space for arguments and align frame before entering C++ world.
__ subq(RSP, Immediate(NumStackArguments(*arg_locations) * kWordSize));
if (OS::ActivationFrameAlignment() > 1) {
__ andq(RSP, Immediate(~(OS::ActivationFrameAlignment() - 1)));
}
// Prepare address for calling the C function.
Address closure_dart = Address(RBP, (num_dart_arguments + 1) * kWordSize);
__ movq(RBX, closure_dart);
__ movq(RBX, FieldAddress(RBX, Closure::context_offset()));
__ movq(RBX, FieldAddress(RBX, Context::variable_offset(0)));
GenerateMarshalInt64(assembler, RBX); // Address is a Smi or Mint.
// Marshal arguments and store in the right register.
for (intptr_t i = 0; i < num_arguments; i++) {
Representation rep = arg_representations->At(i);
Location loc = arg_locations->At(i);
// We do marshalling in the the target register or in RAX.
Register reg = loc.IsRegister() ? loc.reg() : RAX;
// For doubles and floats we use target xmm register or first non param reg.
FpuRegister xmm_reg = loc.IsFpuRegister()
? loc.fpu_reg()
: CallingConventions::xmmFirstNonParameterReg;
// Load parameter from Dart stack.
__ movq(reg, Address(RBP, (num_arguments + 1 - i) * kWordSize));
// Marshal argument.
AbstractType& arg_type =
AbstractType::Handle(signature.ParameterTypeAt(i + 1));
GenerateMarshalArgument(assembler, arg_type, reg, xmm_reg);
// Store marshalled argument where c expects value.
if (loc.IsStackSlot()) {
if (rep == kUnboxedDouble) {
__ movq(reg, xmm_reg);
}
__ movq(loc.ToStackSlotAddress(), reg);
}
}
// Mark that the thread is executing VM code.
__ movq(Assembler::VMTagAddress(), RBX);
__ CallCFunction(RBX);
// Mark that the thread is executing Dart code.
__ movq(Assembler::VMTagAddress(), Immediate(VMTag::kDartCompiledTagId));
// Unmarshal result.
AbstractType& return_type = AbstractType::Handle(signature.result_type());
GenerateUnmarshalResult(assembler, return_type, closure_dart);
// Reset exit frame information in Isolate structure.
__ movq(Address(THR, Thread::top_exit_frame_info_offset()), Immediate(0));
__ LeaveStubFrame();
__ ret();
}
void GenerateFfiInverseTrampoline(Assembler* assembler,
const Function& signature,
void* dart_entry_point) {
ZoneGrowableArray<Representation>* arg_representations =
ffi::ArgumentRepresentations(signature);
ZoneGrowableArray<Location>* arg_locations =
ffi::ArgumentLocations(*arg_representations);
intptr_t num_dart_arguments = signature.num_fixed_parameters();
intptr_t num_arguments = num_dart_arguments - 1; // Ignore closure.
// TODO(dacoharkes): Implement this.
// https://github.com/dart-lang/sdk/issues/35761
// Look at StubCode::GenerateInvokeDartCodeStub.
__ int3();
for (intptr_t i = 0; i < num_arguments; i++) {
Register reg = arg_locations->At(i).reg();
__ SmiTag(reg);
}
__ movq(RBX, Immediate(reinterpret_cast<intptr_t>(dart_entry_point)));
__ int3();
__ call(RBX);
__ int3();
}
} // namespace dart
#endif // defined(TARGET_ARCH_X64) && !defined(DART_PRECOMPILED_RUNTIME)