d36adbacaf
The former contents of the VM isolate are now included into each isolate group. This makes each isolate group's heap independent, and in particular allows each heap to be allocated to a separate pointer cage (not done in this CL). The duplicated stubs that allowed PC relative calls are removed, since the originals can now be the target of PC relative calls. The bootstrapping needing to load an AppJIT or AppAOT snapshot is reduced to allocating the oddballs. The code is entirely dropped in the AOT runtime, but the JIT runtime still has it to allow for flags to affect the compilation of the stub code. Further refactoring might be able to remove this for the JIT runtime too, with only gen_snapshot knowing how to bootstrap. Class serialization no longer distinguishes predefined classes. The page containing null is marked as never-evacuate. null, false and true must not move because the compiler relies on their low bits having certain patterns for some optimizations. (Previously, the entire VM isolate heap never moved.) Compaction is disabled for IA32. Due to register pressure, some stub calls must not use a scratch register and embed the address of Code. The page containing the call-through-safepoint stub is frozen when running with --write-protect-code and the stub is created at runtime (instead of loaded from an AppJIT or AppAOT snapshot). This stub must remain executable even during a safepoint, as a foreign call might during return during a safepoint and only block after the stub directs it to the runtime. The snapshot symbols are renamed to kDartSnapshotData and kDartSnapshotText. There is no need to distinguish the VM isolate's snapshot, and snaphots are per isolate group not per isolate. Aliases with the old names are added to ease migration. Some global flags that were automatically set based on the VM isolate's snapshot are now isolate group flags and automatically set by the isolate group's snapshot. TEST=ci Change-Id: Iee82016057d609112e9b021d178fc3d4d18b5044 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/500621 Reviewed-by: Alexander Markov <alexmarkov@google.com> Reviewed-by: Tess Strickland <sstrickl@google.com> SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com> Commit-Queue: Ryan Macnak <rmacnak@google.com>
381 lines
14 KiB
C++
381 lines
14 KiB
C++
// Copyright (c) 2012, 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/stub_code.h"
|
|
|
|
#include "platform/assert.h"
|
|
#include "platform/globals.h"
|
|
#include "vm/compiler/assembler/disassembler.h"
|
|
#include "vm/flags.h"
|
|
#include "vm/heap/safepoint.h"
|
|
#include "vm/interpreter.h"
|
|
#include "vm/object_store.h"
|
|
#include "vm/snapshot.h"
|
|
#include "vm/virtual_memory.h"
|
|
#include "vm/visitor.h"
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
#include "vm/compiler/aot/precompiler.h"
|
|
#include "vm/compiler/assembler/assembler.h"
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
namespace dart {
|
|
|
|
DECLARE_FLAG(bool, precompiled_mode);
|
|
|
|
#ifdef DART_TARGET_SUPPORTS_PROBE_POINTS
|
|
DEFINE_FLAG(bool,
|
|
generate_probe_points,
|
|
false,
|
|
"Generate probe points for installation of user space probes");
|
|
#endif
|
|
|
|
#if defined(DART_PRECOMPILED_RUNTIME)
|
|
void StubCode::Init() {
|
|
// Stubs will be loaded from the snapshot.
|
|
UNREACHABLE();
|
|
}
|
|
|
|
#else
|
|
|
|
void StubCode::Init() {
|
|
compiler::ObjectPoolBuilder object_pool_builder;
|
|
|
|
// Generate all the stubs.
|
|
static void (compiler::StubCodeCompiler::* const generators[])() = {
|
|
#define STUB_CODE_DECLARE(name) \
|
|
&compiler::StubCodeCompiler::Generate##name##Stub,
|
|
VM_STUB_CODE_LIST(STUB_CODE_DECLARE)
|
|
#undef STUB_CODE_DECLARE
|
|
};
|
|
|
|
for (intptr_t i = 0; i < kNumStubEntries; i++) {
|
|
Roots::stub_handle(i).initRO(
|
|
Generate(StubNames[i], &object_pool_builder, generators[i]));
|
|
}
|
|
|
|
const ObjectPool& object_pool =
|
|
ObjectPool::Handle(ObjectPool::NewFromBuilder(object_pool_builder));
|
|
|
|
for (intptr_t i = 0; i < kNumStubEntries; i++) {
|
|
Roots::stub_handle(i).set_object_pool(object_pool.ptr());
|
|
}
|
|
|
|
#if defined(DART_PRECOMPILER)
|
|
{
|
|
// Set Function owner for UnknownDartCode stub so it pretends to
|
|
// be a Dart code.
|
|
Thread* thread = Thread::Current();
|
|
Zone* zone = thread->zone();
|
|
const auto& signature = FunctionType::Handle(zone, FunctionType::New());
|
|
auto& owner = Object::Handle(zone);
|
|
owner = thread->isolate_group()->class_table()->At(kVoidCid);
|
|
ASSERT(!owner.IsNull());
|
|
owner = Function::New(signature, Object::null_string(),
|
|
UntaggedFunction::kRegularFunction,
|
|
/*is_static=*/true,
|
|
/*is_const=*/false,
|
|
/*is_abstract=*/false,
|
|
/*is_external=*/false,
|
|
/*is_native=*/false, owner, TokenPosition::kNoSource);
|
|
StubCode::UnknownDartCode().set_owner(owner);
|
|
StubCode::UnknownDartCode().set_exception_handlers(
|
|
Object::empty_exception_handlers());
|
|
StubCode::UnknownDartCode().set_pc_descriptors(Object::empty_descriptors());
|
|
ASSERT(StubCode::UnknownDartCode().IsFunctionCode());
|
|
}
|
|
#endif // defined(DART_PRECOMPILER)
|
|
|
|
if (FLAG_write_protect_code) {
|
|
// An FFI call can be executing in CallNativeThroughSafepoint while a
|
|
// safepoint is in progress. It needs to stay executable.
|
|
IsolateGroup::Current()->heap()->old_space()->Freeze(
|
|
Page::Of(StubCode::CallNativeThroughSafepoint().instructions()));
|
|
}
|
|
}
|
|
|
|
#undef STUB_CODE_GENERATE
|
|
#undef STUB_CODE_SET_OBJECT_POOL
|
|
|
|
CodePtr StubCode::Generate(const char* name,
|
|
compiler::ObjectPoolBuilder* object_pool_builder,
|
|
void (compiler::StubCodeCompiler::*GenerateStub)()) {
|
|
auto thread = Thread::Current();
|
|
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
|
|
|
|
compiler::Assembler assembler(object_pool_builder);
|
|
CompilerState compiler_state(thread, /*is_aot=*/FLAG_precompiled_mode,
|
|
/*is_optimizing=*/false);
|
|
Zone* zone = thread->zone();
|
|
auto* pc_descriptors_list = new (zone) DescriptorList(zone);
|
|
compiler::StubCodeCompiler stubCodeCompiler(&assembler, pc_descriptors_list);
|
|
(stubCodeCompiler.*GenerateStub)();
|
|
const Code& code = Code::Handle(
|
|
zone, Code::FinalizeCodeAndNotify(name, nullptr, &assembler,
|
|
Code::PoolAttachment::kNotAttachPool,
|
|
/*optimized=*/false));
|
|
const PcDescriptors& descriptors = PcDescriptors::Handle(
|
|
zone, pc_descriptors_list->FinalizePcDescriptors(code.PayloadStart()));
|
|
code.set_pc_descriptors(descriptors);
|
|
|
|
#ifndef PRODUCT
|
|
if (FLAG_support_disassembler && FLAG_disassemble_stubs) {
|
|
Disassembler::DisassembleStub(name, code);
|
|
}
|
|
#endif // !PRODUCT
|
|
ASSERT(!code.IsNull());
|
|
return code.ptr();
|
|
}
|
|
#endif // defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
bool StubCode::InInvocationStub(Thread* T,
|
|
uword pc,
|
|
bool is_interpreted_frame) {
|
|
// T might differ from the current thread on platforms where profiling is
|
|
// cross thread, like Mac/Windows/Fuchsia.
|
|
Roots* roots = T->isolate_group()->roots();
|
|
if (roots == nullptr) return false;
|
|
|
|
#if defined(DART_DYNAMIC_MODULES)
|
|
if (is_interpreted_frame) {
|
|
// Recognize special marker set up by interpreter in entry frame.
|
|
return Interpreter::IsEntryFrameMarker(
|
|
reinterpret_cast<const KBCInstr*>(pc));
|
|
}
|
|
{
|
|
const Code& stub = roots->x_stub_handle(kInvokeDartCodeFromBytecodeIndex);
|
|
uword entry = Code::StubEntryPointOf(stub.ptr());
|
|
uword size = Code::StubPayloadSizeOf(stub.ptr());
|
|
if ((pc >= entry) && (pc < (entry + size))) {
|
|
return true;
|
|
}
|
|
}
|
|
#endif // defined(DART_DYNAMIC_MODULES)
|
|
const Code& stub = roots->x_stub_handle(kInvokeDartCodeIndex);
|
|
uword entry = Code::StubEntryPointOf(stub.ptr());
|
|
uword size = Code::StubPayloadSizeOf(stub.ptr());
|
|
return (pc >= entry) && (pc < (entry + size));
|
|
}
|
|
|
|
bool StubCode::InJumpToFrameStub(Thread* T, uword pc) {
|
|
// T might differ from the current thread on platforms where profiling is
|
|
// cross thread, like Mac/Windows/Fuchsia.
|
|
Roots* roots = T->isolate_group()->roots();
|
|
if (roots == nullptr) return false;
|
|
|
|
const Code& stub = roots->x_stub_handle(kJumpToFrameIndex);
|
|
if (stub.ptr() == nullptr) {
|
|
return false; // Still bootstrapping.
|
|
}
|
|
uword entry = Code::StubEntryPointOf(stub.ptr());
|
|
uword size = Code::StubPayloadSizeOf(stub.ptr());
|
|
return (pc >= entry) && (pc < (entry + size));
|
|
}
|
|
|
|
#if !defined(DART_PRECOMPILED_RUNTIME)
|
|
ArrayPtr compiler::StubCodeCompiler::BuildStaticCallsTable(
|
|
Zone* zone,
|
|
compiler::UnresolvedPcRelativeCalls* unresolved_calls) {
|
|
if (unresolved_calls->length() == 0) {
|
|
return Array::null();
|
|
}
|
|
const intptr_t array_length =
|
|
unresolved_calls->length() * Code::kSCallTableEntryLength;
|
|
const auto& static_calls_table =
|
|
Array::Handle(zone, Array::New(array_length, Heap::kOld));
|
|
StaticCallsTable entries(static_calls_table);
|
|
auto& kind_type_and_offset = Smi::Handle(zone);
|
|
for (intptr_t i = 0; i < unresolved_calls->length(); i++) {
|
|
auto& unresolved_call = (*unresolved_calls)[i];
|
|
auto call_kind = unresolved_call->is_tail_call() ? Code::kPcRelativeTailCall
|
|
: Code::kPcRelativeCall;
|
|
kind_type_and_offset =
|
|
Smi::New(Code::KindField::encode(call_kind) |
|
|
Code::EntryPointField::encode(Code::kDefaultEntry) |
|
|
Code::OffsetField::encode(unresolved_call->offset()));
|
|
auto view = entries[i];
|
|
view.Set<Code::kSCallTableKindAndOffset>(kind_type_and_offset);
|
|
view.Set<Code::kSCallTableCodeOrTypeTarget>(unresolved_call->target());
|
|
}
|
|
return static_calls_table.ptr();
|
|
}
|
|
|
|
CodePtr StubCode::GetAllocationStubForClass(const Class& cls) {
|
|
Thread* thread = Thread::Current();
|
|
Zone* zone = thread->zone();
|
|
const Error& error =
|
|
Error::Handle(zone, cls.EnsureIsAllocateFinalized(thread));
|
|
ASSERT(error.IsNull());
|
|
switch (cls.id()) {
|
|
case kArrayCid:
|
|
return StubCode::AllocateArray().ptr();
|
|
#if !defined(TARGET_ARCH_IA32)
|
|
case kGrowableObjectArrayCid:
|
|
return StubCode::AllocateGrowableArray().ptr();
|
|
#endif // !defined(TARGET_ARCH_IA32)
|
|
case kContextCid:
|
|
return StubCode::AllocateContext().ptr();
|
|
case kUnhandledExceptionCid:
|
|
return StubCode::AllocateUnhandledException().ptr();
|
|
case kMintCid:
|
|
return StubCode::AllocateMint().ptr();
|
|
case kDoubleCid:
|
|
return StubCode::AllocateDouble().ptr();
|
|
case kFloat32x4Cid:
|
|
return StubCode::AllocateFloat32x4().ptr();
|
|
case kFloat64x2Cid:
|
|
return StubCode::AllocateFloat64x2().ptr();
|
|
case kInt32x4Cid:
|
|
return StubCode::AllocateInt32x4().ptr();
|
|
case kClosureCid:
|
|
return StubCode::AllocateClosure1().ptr();
|
|
case kRecordCid:
|
|
return StubCode::AllocateRecord().ptr();
|
|
}
|
|
Code& stub = Code::Handle(zone, cls.allocation_stub());
|
|
if (stub.IsNull()) {
|
|
compiler::ObjectPoolBuilder object_pool_builder;
|
|
Precompiler* precompiler = Precompiler::Instance();
|
|
|
|
compiler::ObjectPoolBuilder* wrapper =
|
|
precompiler != nullptr ? precompiler->global_object_pool_builder()
|
|
: &object_pool_builder;
|
|
|
|
const auto pool_attachment = FLAG_precompiled_mode
|
|
? Code::PoolAttachment::kNotAttachPool
|
|
: Code::PoolAttachment::kAttachPool;
|
|
|
|
auto zone = thread->zone();
|
|
auto& allocate_object_stub = Code::ZoneHandle(zone);
|
|
auto& allocate_object_parametrized_stub = Code::ZoneHandle(zone);
|
|
if (FLAG_precompiled_mode) {
|
|
allocate_object_stub = StubCode::AllocateObject().ptr();
|
|
allocate_object_parametrized_stub =
|
|
StubCode::AllocateObjectParameterized().ptr();
|
|
}
|
|
|
|
compiler::Assembler assembler(wrapper);
|
|
CompilerState compiler_state(thread, /*is_aot=*/FLAG_precompiled_mode,
|
|
/*is_optimizing=*/false);
|
|
compiler::UnresolvedPcRelativeCalls unresolved_calls;
|
|
const char* name = cls.ToCString();
|
|
compiler::StubCodeCompiler stubCodeCompiler(&assembler, nullptr);
|
|
stubCodeCompiler.GenerateAllocationStubForClass(
|
|
&unresolved_calls, cls, allocate_object_stub,
|
|
allocate_object_parametrized_stub);
|
|
|
|
const auto& static_calls_table =
|
|
Array::Handle(zone, compiler::StubCodeCompiler::BuildStaticCallsTable(
|
|
zone, &unresolved_calls));
|
|
|
|
SafepointWriteRwLocker ml(thread, thread->isolate_group()->program_lock());
|
|
|
|
auto mutator_fun = [&]() {
|
|
stub = Code::FinalizeCode(nullptr, &assembler, pool_attachment,
|
|
/*optimized=*/false,
|
|
/*stats=*/nullptr);
|
|
// Check if some other thread has not already added the stub.
|
|
if (cls.allocation_stub() == Code::null()) {
|
|
stub.set_owner(cls);
|
|
if (!static_calls_table.IsNull()) {
|
|
stub.set_static_calls_target_table(static_calls_table);
|
|
}
|
|
cls.set_allocation_stub(stub);
|
|
}
|
|
};
|
|
|
|
// We have to ensure no mutators are running, because:
|
|
//
|
|
// a) We allocate an instructions object, which might cause us to
|
|
// temporarily flip page protections from (RX -> RW -> RX).
|
|
thread->isolate_group()->RunWithStoppedMutators(mutator_fun);
|
|
|
|
// We notify code observers after finalizing the code in order to be
|
|
// outside a [SafepointOperationScope].
|
|
Code::NotifyCodeObservers(name, stub, /*optimized=*/false);
|
|
#ifndef PRODUCT
|
|
if (FLAG_support_disassembler && FLAG_disassemble_stubs) {
|
|
Disassembler::DisassembleStub(name, stub);
|
|
}
|
|
#endif // !PRODUCT
|
|
}
|
|
return stub.ptr();
|
|
}
|
|
|
|
CodePtr StubCode::GetAllocationStubForTypedData(classid_t class_id) {
|
|
switch (class_id) {
|
|
case kTypedDataInt8ArrayCid:
|
|
return StubCode::AllocateInt8Array().ptr();
|
|
case kTypedDataUint8ArrayCid:
|
|
return StubCode::AllocateUint8Array().ptr();
|
|
case kTypedDataUint8ClampedArrayCid:
|
|
return StubCode::AllocateUint8ClampedArray().ptr();
|
|
case kTypedDataInt16ArrayCid:
|
|
return StubCode::AllocateInt16Array().ptr();
|
|
case kTypedDataUint16ArrayCid:
|
|
return StubCode::AllocateUint16Array().ptr();
|
|
case kTypedDataInt32ArrayCid:
|
|
return StubCode::AllocateInt32Array().ptr();
|
|
case kTypedDataUint32ArrayCid:
|
|
return StubCode::AllocateUint32Array().ptr();
|
|
case kTypedDataInt64ArrayCid:
|
|
return StubCode::AllocateInt64Array().ptr();
|
|
case kTypedDataUint64ArrayCid:
|
|
return StubCode::AllocateUint64Array().ptr();
|
|
case kTypedDataFloat32ArrayCid:
|
|
return StubCode::AllocateFloat32Array().ptr();
|
|
case kTypedDataFloat64ArrayCid:
|
|
return StubCode::AllocateFloat64Array().ptr();
|
|
case kTypedDataFloat32x4ArrayCid:
|
|
return StubCode::AllocateFloat32x4Array().ptr();
|
|
case kTypedDataInt32x4ArrayCid:
|
|
return StubCode::AllocateInt32x4Array().ptr();
|
|
case kTypedDataFloat64x2ArrayCid:
|
|
return StubCode::AllocateFloat64x2Array().ptr();
|
|
}
|
|
UNREACHABLE();
|
|
return Code::null();
|
|
}
|
|
#endif // !defined(DART_PRECOMPILED_RUNTIME)
|
|
|
|
const Code& StubCode::UnoptimizedStaticCallEntry(intptr_t num_args_tested) {
|
|
switch (num_args_tested) {
|
|
case 0:
|
|
return ZeroArgsUnoptimizedStaticCall();
|
|
case 1:
|
|
return OneArgUnoptimizedStaticCall();
|
|
case 2:
|
|
return TwoArgsUnoptimizedStaticCall();
|
|
default:
|
|
UNIMPLEMENTED();
|
|
return Code::Handle();
|
|
}
|
|
}
|
|
|
|
void StubCode::ForEachStub(
|
|
const std::function<bool(const char*, uword)>& callback) {
|
|
for (intptr_t i = 0; i < kNumStubEntries; i++) {
|
|
if (Roots::stub_handle(i).ptr() != nullptr) {
|
|
if (!callback(StubNames[i], Roots::stub_handle(i).EntryPoint())) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const char* StubCode::NameOfStub(uword entry_point) {
|
|
const char* result = nullptr;
|
|
ForEachStub(
|
|
[&result, &entry_point](const char* name, uword stub_entry_point) {
|
|
if (stub_entry_point == entry_point) {
|
|
result = name;
|
|
return false; // Found match.
|
|
}
|
|
return true; // Continue searching.
|
|
});
|
|
return result;
|
|
}
|
|
|
|
} // namespace dart
|