Commit b4587369 authored by jgruber's avatar jgruber Committed by Commit Bot

[snapshot] Lazy-deserialize bytecode handlers

Add support for interpreter bytecode handlers that are deserialized
lazily immediately before they are first used.

Design doc: http://goo.gl/QxZBL2

Bug: v8:6624
Change-Id: Id68844ed14e76ca781b0bfe42c25a94b4fed1ae5
Reviewed-on: https://chromium-review.googlesource.com/750982
Commit-Queue: Jakob Gruber <jgruber@chromium.org>
Reviewed-by: 's avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: 's avatarYang Guo <yangguo@chromium.org>
Reviewed-by: 's avatarMichael Lippautz <mlippautz@chromium.org>
Reviewed-by: 's avatarMythri Alle <mythria@chromium.org>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Cr-Commit-Position: refs/heads/master@{#49220}
parent 17eda5f9
......@@ -960,6 +960,10 @@ DEFINE_VALUE_IMPLICATION(runtime_call_stats, runtime_stats, 1)
// snapshot-common.cc
DEFINE_BOOL(lazy_deserialization, true,
"Deserialize code lazily from the snapshot.")
DEFINE_BOOL(lazy_handler_deserialization, false,
"Deserialize bytecode handlers lazily from the snapshot.")
DEFINE_IMPLICATION(lazy_handler_deserialization, lazy_deserialization)
DEFINE_IMPLICATION(future, lazy_handler_deserialization)
DEFINE_BOOL(trace_lazy_deserialization, false, "Trace lazy deserialization.")
DEFINE_BOOL(profile_deserialization, false,
"Print the time it takes to deserialize the snapshot.")
......
......@@ -2581,6 +2581,9 @@ bool Heap::RootCanBeWrittenAfterInitialization(Heap::RootListIndex root_index) {
case kApiSymbolTableRootIndex:
case kApiPrivateSymbolTableRootIndex:
case kMessageListenersRootIndex:
case kDeserializeLazyHandlerRootIndex:
case kDeserializeLazyHandlerWideRootIndex:
case kDeserializeLazyHandlerExtraWideRootIndex:
// Smi values
#define SMI_ENTRY(type, name, Name) case k##Name##RootIndex:
SMI_ROOT_LIST(SMI_ENTRY)
......@@ -6245,6 +6248,23 @@ void Heap::UnregisterStrongRoots(Object** start) {
}
}
bool Heap::IsDeserializeLazyHandler(Code* code) {
return (code == deserialize_lazy_handler() ||
code == deserialize_lazy_handler_wide() ||
code == deserialize_lazy_handler_extra_wide());
}
void Heap::SetDeserializeLazyHandler(Code* code) {
set_deserialize_lazy_handler(code);
}
void Heap::SetDeserializeLazyHandlerWide(Code* code) {
set_deserialize_lazy_handler_wide(code);
}
void Heap::SetDeserializeLazyHandlerExtraWide(Code* code) {
set_deserialize_lazy_handler_extra_wide(code);
}
size_t Heap::NumberOfTrackedHeapObjectTypes() {
return ObjectStats::OBJECT_STATS_COUNT;
......
......@@ -240,6 +240,11 @@ using v8::MemoryPressureLevel;
V(FixedArray, serialized_templates, SerializedTemplates) \
V(FixedArray, serialized_global_proxy_sizes, SerializedGlobalProxySizes) \
V(TemplateList, message_listeners, MessageListeners) \
/* DeserializeLazy handlers for lazy bytecode deserialization */ \
V(Object, deserialize_lazy_handler, DeserializeLazyHandler) \
V(Object, deserialize_lazy_handler_wide, DeserializeLazyHandlerWide) \
V(Object, deserialize_lazy_handler_extra_wide, \
DeserializeLazyHandlerExtraWide) \
/* JS Entries */ \
V(Code, js_entry_code, JsEntryCode) \
V(Code, js_construct_entry_code, JsConstructEntryCode)
......@@ -1092,6 +1097,11 @@ class Heap {
void RegisterStrongRoots(Object** start, Object** end);
void UnregisterStrongRoots(Object** start);
bool IsDeserializeLazyHandler(Code* code);
void SetDeserializeLazyHandler(Code* code);
void SetDeserializeLazyHandlerWide(Code* code);
void SetDeserializeLazyHandlerExtraWide(Code* code);
// ===========================================================================
// Inline allocation. ========================================================
// ===========================================================================
......
......@@ -11,6 +11,7 @@
#include "src/factory.h"
#include "src/heap-symbols.h"
#include "src/heap/heap.h"
#include "src/interpreter/interpreter.h"
#include "src/isolate.h"
#include "src/layout-descriptor.h"
#include "src/lookup-cache.h"
......@@ -621,6 +622,11 @@ void Heap::CreateInitialObjects() {
set_noscript_shared_function_infos(Smi::kZero);
STATIC_ASSERT(interpreter::BytecodeOperands::kOperandScaleCount == 3);
set_deserialize_lazy_handler(Smi::kZero);
set_deserialize_lazy_handler_wide(Smi::kZero);
set_deserialize_lazy_handler_extra_wide(Smi::kZero);
// Initialize context slot cache.
isolate_->context_slot_cache()->Clear();
......
......@@ -8,6 +8,7 @@
#include <cstdint>
#include <iosfwd>
#include <string>
#include <vector>
#include "src/globals.h"
#include "src/interpreter/bytecode-operands.h"
......@@ -675,6 +676,12 @@ class V8_EXPORT_PRIVATE Bytecodes final : public AllStatic {
bytecode == Bytecode::kDebugBreakWide;
}
// Returns true if the bytecode can be lazily deserialized.
static constexpr bool IsLazy(Bytecode bytecode) {
// Currently, all handlers are deserialized lazily.
return true;
}
// Returns the number of values which |bytecode| returns.
static constexpr size_t ReturnCount(Bytecode bytecode) {
return bytecode == Bytecode::kReturn ? 1 : 0;
......@@ -853,6 +860,26 @@ class V8_EXPORT_PRIVATE Bytecodes final : public AllStatic {
}
}
static std::vector<Bytecode> AllBytecodesUsingHandler(Bytecode bytecode) {
Bytecode dummy;
USE(dummy);
switch (bytecode) {
case Bytecode::kLdaContextSlot:
DCHECK(
ReusesExistingHandler(Bytecode::kLdaImmutableContextSlot, &dummy));
DCHECK_EQ(bytecode, dummy);
return {bytecode, Bytecode::kLdaImmutableContextSlot};
case Bytecode::kLdaCurrentContextSlot:
DCHECK(ReusesExistingHandler(Bytecode::kLdaImmutableCurrentContextSlot,
&dummy));
DCHECK_EQ(bytecode, dummy);
return {bytecode, Bytecode::kLdaImmutableCurrentContextSlot};
default:
DCHECK(!ReusesExistingHandler(bytecode, &dummy));
return {bytecode};
}
}
// Returns the size of |operand_type| for |operand_scale|.
static OperandSize SizeOfOperand(OperandType operand_type,
OperandScale operand_scale) {
......
......@@ -1527,6 +1527,18 @@ void InterpreterAssembler::ToNumberOrNumeric(Object::Conversion mode) {
Dispatch();
}
void InterpreterAssembler::DeserializeLazyAndDispatch() {
Node* context = GetContext();
Node* bytecode_offset = BytecodeOffset();
Node* bytecode = LoadBytecode(bytecode_offset);
Node* target_handler =
CallRuntime(Runtime::kInterpreterDeserializeLazy, context,
SmiTag(bytecode), SmiConstant(operand_scale()));
DispatchToBytecodeHandler(target_handler, bytecode_offset);
}
} // namespace interpreter
} // namespace internal
} // namespace v8
......@@ -245,6 +245,9 @@ class V8_EXPORT_PRIVATE InterpreterAssembler : public CodeStubAssembler {
void ToNumberOrNumeric(Object::Conversion mode);
// Lazily deserializes the current bytecode's handler and tail-calls into it.
void DeserializeLazyAndDispatch();
private:
// Returns a tagged pointer to the current function's BytecodeArray object.
compiler::Node* BytecodeArrayTaggedPointer();
......
......@@ -3243,6 +3243,71 @@ Handle<Code> GenerateBytecodeHandler(Isolate* isolate, Bytecode bytecode,
return code;
}
namespace {
// DeserializeLazy
//
// Deserialize the bytecode handler, store it in the dispatch table, and
// finally jump there (preserving existing args).
// We manually create a custom assembler instead of using the helper macros
// above since no corresponding bytecode exists.
class DeserializeLazyAssembler : public InterpreterAssembler {
public:
static const Bytecode kFakeBytecode = Bytecode::kIllegal;
explicit DeserializeLazyAssembler(compiler::CodeAssemblerState* state,
OperandScale operand_scale)
: InterpreterAssembler(state, kFakeBytecode, operand_scale) {}
static void Generate(compiler::CodeAssemblerState* state,
OperandScale operand_scale) {
DeserializeLazyAssembler assembler(state, operand_scale);
state->SetInitialDebugInformation("DeserializeLazy", __FILE__, __LINE__);
assembler.GenerateImpl();
}
private:
void GenerateImpl() { DeserializeLazyAndDispatch(); }
DISALLOW_COPY_AND_ASSIGN(DeserializeLazyAssembler);
};
} // namespace
Handle<Code> GenerateDeserializeLazyHandler(Isolate* isolate,
OperandScale operand_scale) {
Zone zone(isolate->allocator(), ZONE_NAME);
const size_t return_count = 0;
std::string debug_name = std::string("DeserializeLazy");
if (operand_scale > OperandScale::kSingle) {
Bytecode prefix_bytecode =
Bytecodes::OperandScaleToPrefixBytecode(operand_scale);
debug_name = debug_name.append(Bytecodes::ToString(prefix_bytecode));
}
InterpreterDispatchDescriptor descriptor(isolate);
compiler::CodeAssemblerState state(isolate, &zone, descriptor,
Code::BYTECODE_HANDLER, debug_name.c_str(),
return_count);
DeserializeLazyAssembler::Generate(&state, operand_scale);
Handle<Code> code = compiler::CodeAssembler::GenerateCode(&state);
PROFILE(isolate,
CodeCreateEvent(CodeEventListener::BYTECODE_HANDLER_TAG,
AbstractCode::cast(*code), debug_name.c_str()));
#ifdef ENABLE_DISASSEMBLER
if (FLAG_trace_ignition_codegen) {
OFStream os(stdout);
code->Disassemble(debug_name.c_str(), os);
os << std::flush;
}
#endif // ENABLE_DISASSEMBLER
return code;
}
} // namespace interpreter
} // namespace internal
} // namespace v8
......@@ -15,6 +15,9 @@ namespace interpreter {
extern Handle<Code> GenerateBytecodeHandler(Isolate* isolate, Bytecode bytecode,
OperandScale operand_scale);
extern Handle<Code> GenerateDeserializeLazyHandler(Isolate* isolate,
OperandScale operand_scale);
} // namespace interpreter
} // namespace internal
} // namespace v8
......
......@@ -19,6 +19,7 @@
#include "src/objects/shared-function-info.h"
#include "src/parsing/parse-info.h"
#include "src/setup-isolate.h"
#include "src/snapshot/snapshot.h"
#include "src/visitors.h"
namespace v8 {
......@@ -87,6 +88,44 @@ Interpreter::Interpreter(Isolate* isolate) : isolate_(isolate) {
}
}
Code* Interpreter::GetAndMaybeDeserializeBytecodeHandler(
Bytecode bytecode, OperandScale operand_scale) {
Code* code = GetBytecodeHandler(bytecode, operand_scale);
// Already deserialized? Then just return the handler.
if (!isolate_->heap()->IsDeserializeLazyHandler(code)) return code;
DCHECK(FLAG_lazy_handler_deserialization);
if (FLAG_trace_lazy_deserialization) {
PrintF("Lazy-deserializing handler %s\n",
Bytecodes::ToString(bytecode, operand_scale).c_str());
}
// Some handlers are reused for several bytecodes. If we encounter such a
// bytecode, find the canonical handler, deserialize it, and write it into all
// slots in the dispatch table that (re)use it.
Bytecode maybe_reused_bytecode;
const bool reuses_existing_handler =
Bytecodes::ReusesExistingHandler(bytecode, &maybe_reused_bytecode);
Bytecode handler_bytecode =
reuses_existing_handler ? maybe_reused_bytecode : bytecode;
DCHECK(Bytecodes::BytecodeHasHandler(handler_bytecode, operand_scale));
code =
Snapshot::DeserializeHandler(isolate_, handler_bytecode, operand_scale);
DCHECK(code->IsCode());
DCHECK_EQ(code->kind(), Code::BYTECODE_HANDLER);
for (Bytecode b : Bytecodes::AllBytecodesUsingHandler(handler_bytecode)) {
SetBytecodeHandler(b, operand_scale, code);
}
return code;
}
Code* Interpreter::GetBytecodeHandler(Bytecode bytecode,
OperandScale operand_scale) {
DCHECK(IsDispatchTableInitialized());
......
......@@ -42,6 +42,11 @@ class Interpreter {
FunctionLiteral* literal,
Isolate* isolate);
// If the bytecode handler for |bytecode| and |operand_scale| has not yet
// been loaded, deserialize it. Then return the handler.
Code* GetAndMaybeDeserializeBytecodeHandler(Bytecode bytecode,
OperandScale operand_scale);
// Return bytecode handler for |bytecode| and |operand_scale|.
Code* GetBytecodeHandler(Bytecode bytecode, OperandScale operand_scale);
......
......@@ -56,6 +56,18 @@ void SetupInterpreter::InstallBytecodeHandlers(Interpreter* interpreter) {
}
}
// Generate the DeserializeLazy handlers, one for each operand scale.
Heap* heap = interpreter->isolate_->heap();
DCHECK_EQ(Smi::kZero, heap->deserialize_lazy_handler());
heap->SetDeserializeLazyHandler(*GenerateDeserializeLazyHandler(
interpreter->isolate_, OperandScale::kSingle));
DCHECK_EQ(Smi::kZero, heap->deserialize_lazy_handler_wide());
heap->SetDeserializeLazyHandlerWide(*GenerateDeserializeLazyHandler(
interpreter->isolate_, OperandScale::kDouble));
DCHECK_EQ(Smi::kZero, heap->deserialize_lazy_handler_extra_wide());
heap->SetDeserializeLazyHandlerExtraWide(*GenerateDeserializeLazyHandler(
interpreter->isolate_, OperandScale::kQuadruple));
// Initialization should have been successful.
DCHECK(interpreter->IsDispatchTableInitialized());
}
......
......@@ -1598,6 +1598,7 @@ void Logger::LogBytecodeHandlers() {
interpreter::Bytecode bytecode = interpreter::Bytecodes::FromByte(index);
if (interpreter::Bytecodes::BytecodeHasHandler(bytecode, operand_scale)) {
Code* code = interpreter->GetBytecodeHandler(bytecode, operand_scale);
if (isolate_->heap()->IsDeserializeLazyHandler(code)) continue;
std::string bytecode_name =
interpreter::Bytecodes::ToString(bytecode, operand_scale);
PROFILE(isolate_, CodeCreateEvent(
......
......@@ -21,12 +21,17 @@
#include "src/isolate-inl.h"
#include "src/objects/debug-objects-inl.h"
#include "src/runtime/runtime.h"
#include "src/snapshot/snapshot.h"
#include "src/wasm/wasm-objects-inl.h"
namespace v8 {
namespace internal {
RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) {
using interpreter::Bytecode;
using interpreter::Bytecodes;
using interpreter::OperandScale;
SealHandleScope shs(isolate);
DCHECK_EQ(1, args.length());
CONVERT_ARG_HANDLE_CHECKED(Object, value, 0);
......@@ -47,20 +52,22 @@ RUNTIME_FUNCTION_RETURN_PAIR(Runtime_DebugBreakOnBytecode) {
SharedFunctionInfo* shared = interpreted_frame->function()->shared();
BytecodeArray* bytecode_array = shared->bytecode_array();
int bytecode_offset = interpreted_frame->GetBytecodeOffset();
interpreter::Bytecode bytecode =
interpreter::Bytecodes::FromByte(bytecode_array->get(bytecode_offset));
if (bytecode == interpreter::Bytecode::kReturn) {
Bytecode bytecode = Bytecodes::FromByte(bytecode_array->get(bytecode_offset));
if (bytecode == Bytecode::kReturn) {
// If we are returning, reset the bytecode array on the interpreted stack
// frame to the non-debug variant so that the interpreter entry trampoline
// sees the return bytecode rather than the DebugBreak.
interpreted_frame->PatchBytecodeArray(bytecode_array);
}
// We do not have to deal with operand scale here. If the bytecode at the
// break is prefixed by operand scaling, we would have patched over the
// scaling prefix. We now simply dispatch to the handler for the prefix.
return MakePair(isolate->debug()->return_value(),
isolate->interpreter()->GetBytecodeHandler(
bytecode, interpreter::OperandScale::kSingle));
OperandScale operand_scale = OperandScale::kSingle;
Code* code = isolate->interpreter()->GetAndMaybeDeserializeBytecodeHandler(
bytecode, operand_scale);
return MakePair(isolate->debug()->return_value(), code);
}
......
......@@ -526,7 +526,7 @@ RUNTIME_FUNCTION(Runtime_DeserializeLazy) {
DCHECK_EQ(Builtins::TFJ, Builtins::KindOf(builtin_id));
if (FLAG_trace_lazy_deserialization) {
PrintF("Lazy-deserializing %s\n", Builtins::name(builtin_id));
PrintF("Lazy-deserializing builtin %s\n", Builtins::name(builtin_id));
}
Code* code = Snapshot::DeserializeBuiltin(isolate, builtin_id);
......
......@@ -13,12 +13,34 @@
#include "src/interpreter/bytecode-flags.h"
#include "src/interpreter/bytecode-register.h"
#include "src/interpreter/bytecodes.h"
#include "src/interpreter/interpreter.h"
#include "src/isolate-inl.h"
#include "src/ostreams.h"
#include "src/snapshot/snapshot.h"
namespace v8 {
namespace internal {
RUNTIME_FUNCTION(Runtime_InterpreterDeserializeLazy) {
HandleScope scope(isolate);
DCHECK(FLAG_lazy_handler_deserialization);
DCHECK(FLAG_lazy_deserialization);
DCHECK_EQ(2, args.length());
CONVERT_SMI_ARG_CHECKED(bytecode_int, 0);
CONVERT_SMI_ARG_CHECKED(operand_scale_int, 1);
using interpreter::Bytecode;
using interpreter::Bytecodes;
using interpreter::OperandScale;
Bytecode bytecode = Bytecodes::FromByte(bytecode_int);
OperandScale operand_scale = static_cast<OperandScale>(operand_scale_int);
return isolate->interpreter()->GetAndMaybeDeserializeBytecodeHandler(
bytecode, operand_scale);
}
RUNTIME_FUNCTION(Runtime_InterpreterNewClosure) {
HandleScope scope(isolate);
DCHECK_EQ(4, args.length());
......
......@@ -224,6 +224,7 @@ namespace internal {
#define FOR_EACH_INTRINSIC_INTERPRETER(F) \
FOR_EACH_INTRINSIC_INTERPRETER_TRACE(F) \
FOR_EACH_INTRINSIC_INTERPRETER_TRACE_FEEDBACK(F) \
F(InterpreterDeserializeLazy, 2, 1) \
F(InterpreterNewClosure, 4, 1)
#define FOR_EACH_INTRINSIC_FUNCTION(F) \
......
......@@ -12,15 +12,23 @@
namespace v8 {
namespace internal {
using interpreter::Bytecode;
using interpreter::Bytecodes;
using interpreter::Interpreter;
using interpreter::OperandScale;
BuiltinDeserializerAllocator::BuiltinDeserializerAllocator(
Deserializer<BuiltinDeserializerAllocator>* deserializer)
: deserializer_(deserializer) {}
BuiltinDeserializerAllocator::~BuiltinDeserializerAllocator() {
delete handler_allocations_;
}
namespace {
int HandlerAllocationIndex(int code_object_id) {
return code_object_id - BuiltinSnapshotUtils::kFirstHandlerIndex;
}
} // namespace
Address BuiltinDeserializerAllocator::Allocate(AllocationSpace space,
int size) {
const int code_object_id = deserializer()->CurrentCodeObjectId();
......@@ -36,18 +44,18 @@ Address BuiltinDeserializerAllocator::Allocate(AllocationSpace space,
DCHECK(Internals::HasHeapObjectTag(obj));
return HeapObject::cast(obj)->address();
} else if (BSU::IsHandlerIndex(code_object_id)) {
Bytecode bytecode;
OperandScale operand_scale;
std::tie(bytecode, operand_scale) = BSU::BytecodeFromIndex(code_object_id);
Address* dispatch_table = isolate()->interpreter()->dispatch_table_;
const size_t index =
Interpreter::GetDispatchTableIndex(bytecode, operand_scale);
Object* obj = HeapObject::FromAddress(dispatch_table[index]);
DCHECK(Internals::HasHeapObjectTag(obj));
return HeapObject::cast(obj)->address();
if (handler_allocation_ != nullptr) {
// Lazy deserialization.
DCHECK_NULL(handler_allocations_);
return handler_allocation_;
} else {
// Eager deserialization.
DCHECK_NULL(handler_allocation_);
DCHECK_NOT_NULL(handler_allocations_);
int index = HandlerAllocationIndex(code_object_id);
DCHECK_NOT_NULL(handler_allocations_->at(index));
return handler_allocations_->at(index);
}
}
UNREACHABLE();
......@@ -59,8 +67,8 @@ BuiltinDeserializerAllocator::CreateReservationsForEagerBuiltinsAndHandlers() {
// Reservations for builtins.
// DeserializeLazy is always the first reservation (to simplify logic in
// InitializeBuiltinsTable).
// DeserializeLazy is always the first builtin reservation (to simplify logic
// in InitializeBuiltinsTable).
{
DCHECK(!Builtins::IsLazy(Builtins::kDeserializeLazy));
uint32_t builtin_size =
......@@ -73,7 +81,7 @@ BuiltinDeserializerAllocator::CreateReservationsForEagerBuiltinsAndHandlers() {
if (i == Builtins::kDeserializeLazy) continue;
// Skip lazy builtins. These will be replaced by the DeserializeLazy code
// object in InitializeBuiltinsTable and thus require no reserved space.
// object in InitializeFromReservations and thus require no reserved space.
if (deserializer()->IsLazyDeserializationEnabled() && Builtins::IsLazy(i)) {
continue;
}
......@@ -87,9 +95,17 @@ BuiltinDeserializerAllocator::CreateReservationsForEagerBuiltinsAndHandlers() {
BSU::ForEachBytecode(
[=, &result](Bytecode bytecode, OperandScale operand_scale) {
// TODO(jgruber): Replace with DeserializeLazy handler.
if (!BSU::BytecodeHasDedicatedHandler(bytecode, operand_scale)) return;
if (!BSU::BytecodeHasDedicatedHandler(bytecode, operand_scale)) {
// Bytecodes without a handler don't require a reservation.
return;
} else if (FLAG_lazy_handler_deserialization &&
deserializer()->IsLazyDeserializationEnabled() &&
Bytecodes::IsLazy(bytecode)) {
// Skip lazy handlers. These will be replaced by the DeserializeLazy
// code object in InitializeFromReservations and thus require no
// reserved space.
return;
}
const int index = BSU::BytecodeToIndex(bytecode, operand_scale);
uint32_t handler_size = deserializer()->ExtractCodeObjectSize(index);
......@@ -124,14 +140,10 @@ void BuiltinDeserializerAllocator::InitializeHandlerFromReservation(
SkipList::Update(chunk.start, chunk.size);
Address* dispatch_table = isolate()->interpreter()->dispatch_table_;
const size_t index =
Interpreter::GetDispatchTableIndex(bytecode, operand_scale);
// At this point, the HeapObject is not yet a Code object, and thus we don't
// initialize with code->entry() here. Once deserialization completes, this
// is overwritten with the final code->entry() value.
dispatch_table[index] = chunk.start;
DCHECK_NOT_NULL(handler_allocations_);
const int index =
HandlerAllocationIndex(BSU::BytecodeToIndex(bytecode, operand_scale));
handler_allocations_->at(index) = chunk.start;
#ifdef DEBUG
RegisterCodeObjectReservation(BSU::BytecodeToIndex(bytecode, operand_scale));
......@@ -169,13 +181,23 @@ void BuiltinDeserializerAllocator::InitializeFromReservations(
}
}
// Initialize the interpreter dispatch table.
// Initialize interpreter bytecode handler reservations.
DCHECK_NULL(handler_allocations_);
handler_allocations_ = new std::vector<Address>(BSU::kNumberOfHandlers);
BSU::ForEachBytecode(
[=, &reservation_index](Bytecode bytecode, OperandScale operand_scale) {
// TODO(jgruber): Replace with DeserializeLazy handler.
if (!BSU::BytecodeHasDedicatedHandler(bytecode, operand_scale)) {
// Bytecodes without a handler don't have a reservation.
return;
} else if (FLAG_lazy_handler_deserialization &&
deserializer()->IsLazyDeserializationEnabled() &&
Bytecodes::IsLazy(bytecode)) {
// Likewise, bytecodes with lazy handlers don't either.
return;
}
if (!BSU::BytecodeHasDedicatedHandler(bytecode, operand_scale)) return;
InitializeHandlerFromReservation(reservation[reservation_index],
bytecode, operand_scale);
reservation_index++;
......@@ -214,6 +236,28 @@ void BuiltinDeserializerAllocator::ReserveAndInitializeBuiltinsTableForBuiltin(
#endif
}
void BuiltinDeserializerAllocator::ReserveForHandler(
Bytecode bytecode, OperandScale operand_scale) {
DCHECK(AllowHeapAllocation::IsAllowed());
DCHECK(isolate()->interpreter()->IsDispatchTableInitialized());
const int code_object_id = BSU::BytecodeToIndex(bytecode, operand_scale);
const uint32_t handler_size =
deserializer()->ExtractCodeObjectSize(code_object_id);
DCHECK_LE(handler_size, MemoryAllocator::PageAreaSize(CODE_SPACE));
handler_allocation_ =
isolate()->factory()->NewCodeForDeserialization(handler_size)->address();
// Note: After this point and until deserialization finishes, heap allocation
// is disallowed. We currently can't safely assert this since we'd need to
// pass the DisallowHeapAllocation scope out of this function.
#ifdef DEBUG
RegisterCodeObjectReservation(code_object_id);
#endif
}
#ifdef DEBUG
void BuiltinDeserializerAllocator::RegisterCodeObjectReservation(
int code_object_id) {
......
......@@ -9,6 +9,7 @@
#include "src/globals.h"
#include "src/heap/heap.h"
#include "src/interpreter/interpreter.h"
#include "src/snapshot/serializer-common.h"
namespace v8 {
......@@ -22,11 +23,15 @@ class BuiltinSnapshotUtils;
class BuiltinDeserializerAllocator final {
using BSU = BuiltinSnapshotUtils;
using Bytecode = interpreter::Bytecode;
using OperandScale = interpreter::OperandScale;
public:
BuiltinDeserializerAllocator(
Deserializer<BuiltinDeserializerAllocator>* deserializer);
~BuiltinDeserializerAllocator();
// ------- Allocation Methods -------
// Methods related to memory allocation during deserialization.
......@@ -37,8 +42,16 @@ class BuiltinDeserializerAllocator final {
// deserialization) in order to avoid having to patch builtin references
// later on. See also the kBuiltin case in deserializer.cc.
//
// There are three ways that we use to reserve / allocate space. In all
// cases, required objects are requested from the GC prior to
// deserialization. 1. pre-allocated builtin code objects are written into
// the builtins table (this is to make deserialization of builtin references
// easier). Pre-allocated handler code objects are 2. stored in the
// {handler_allocations_} vector (at eager-deserialization time) and 3.
// stored in {handler_allocation_} (at lazy-deserialization time).
//
// Allocate simply returns the pre-allocated object prepared by
// InitializeBuiltinsTable.
// InitializeFromReservations.
Address Allocate(AllocationSpace space, int size);
void MoveToNextChunk(AllocationSpace space) { UNREACHABLE(); }
......@@ -69,6 +82,10 @@ class BuiltinDeserializerAllocator final {
// lazily deserializing a single builtin.
void ReserveAndInitializeBuiltinsTableForBuiltin(int builtin_id);
// Pre-allocates a code object preparation for lazily deserializing a single
// handler.
void ReserveForHandler(Bytecode bytecode, OperandScale operand_scale);
#ifdef DEBUG
bool ReservationsAreFullyUsed() const;
#endif
......@@ -105,6 +122,13 @@ class BuiltinDeserializerAllocator final {
// construction since that makes vtable-based checks fail.
Deserializer<BuiltinDeserializerAllocator>* const deserializer_;
// Stores allocated space for bytecode handlers during eager deserialization.
std::vector<Address>* handler_allocations_ = nullptr;
// Stores the allocated space for a single handler during lazy
// deserialization.
Address handler_allocation_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(BuiltinDeserializerAllocator)
};
......
......@@ -12,10 +12,8 @@
namespace v8 {
namespace internal {
using interpreter::Bytecode;
using interpreter::Bytecodes;
using interpreter::Interpreter;
using interpreter::OperandScale;
// Tracks the code object currently being deserialized (required for
// allocation).
......@@ -60,7 +58,7 @@ void BuiltinDeserializer::DeserializeEagerBuiltinsAndHandlers() {
for (int i = 0; i < BSU::kNumberOfBuiltins; i++) {
if (IsLazyDeserializationEnabled() && Builtins::IsLazy(i)) {
// Do nothing. These builtins have been replaced by DeserializeLazy in
// InitializeBuiltinsTable.
// InitializeFromReservations.
DCHECK_EQ(builtins->builtin(Builtins::kDeserializeLazy),
builtins->builtin(i));
} else {
......@@ -77,17 +75,21 @@ void BuiltinDeserializer::DeserializeEagerBuiltinsAndHandlers() {
// Deserialize bytecode handlers.
// The dispatch table has been initialized during memory reservation.
Interpreter* interpreter = isolate()->interpreter();
DCHECK(isolate()->interpreter()->IsDispatchTableInitialized());
DCHECK(!isolate()->interpreter()->IsDispatchTableInitialized());
BSU::ForEachBytecode([=](Bytecode bytecode, OperandScale operand_scale) {
// TODO(jgruber): Replace with DeserializeLazy handler.
// Bytecodes without a dedicated handler are patched up in a second pass.
if (!BSU::BytecodeHasDedicatedHandler(bytecode, operand_scale)) return;
Code* code = DeserializeHandlerRaw(bytecode, operand_scale);
// If lazy-deserialization is enabled and the current bytecode is lazy,
// we write the generic LazyDeserialization handler into the dispatch table
// and deserialize later upon first use.
Code* code = (FLAG_lazy_handler_deserialization &&
IsLazyDeserializationEnabled() && Bytecodes::IsLazy(bytecode))
? GetDeserializeLazyHandler(operand_scale)
: DeserializeHandlerRaw(bytecode, operand_scale);
interpreter->SetBytecodeHandler(bytecode, operand_scale, code);
});
......@@ -123,6 +125,13 @@ Code* BuiltinDeserializer::DeserializeBuiltin(int builtin_id) {
return DeserializeBuiltinRaw(builtin_id);
}
Code* BuiltinDeserializer::DeserializeHandler(Bytecode bytecode,
OperandScale operand_scale) {
allocator()->ReserveForHandler(bytecode, operand_scale);
DisallowHeapAllocation no_gc;
return DeserializeHandlerRaw(bytecode, operand_scale);
}
Code* BuiltinDeserializer::DeserializeBuiltinRaw(int builtin_id) {
DCHECK(!AllowHeapAllocation::IsAllowed());
DCHECK(Builtins::IsBuiltinId(builtin_id));
......@@ -190,5 +199,20 @@ uint32_t BuiltinDeserializer::ExtractCodeObjectSize(int code_object_id) {
return result;
}
Code* BuiltinDeserializer::GetDeserializeLazyHandler(
interpreter::OperandScale operand_scale) const {
STATIC_ASSERT(interpreter::BytecodeOperands::kOperandScaleCount == 3);
switch (operand_scale) {
case OperandScale::kSingle:
return Code::cast(isolate()->heap()->deserialize_lazy_handler());
case OperandScale::kDouble:
return Code::cast(isolate()->heap()->deserialize_lazy_handler_wide());
case OperandScale::kQuadruple:
return Code::cast(
isolate()->heap()->deserialize_lazy_handler_extra_wide());
}
UNREACHABLE();
}
} // namespace internal
} // namespace v8
......@@ -19,6 +19,8 @@ class BuiltinSnapshotData;
class BuiltinDeserializer final
: public Deserializer<BuiltinDeserializerAllocator> {
using BSU = BuiltinSnapshotUtils;
using Bytecode = interpreter::Bytecode;
using OperandScale = interpreter::OperandScale;
public:
BuiltinDeserializer(Isolate* isolate, const BuiltinSnapshotData* data);
......@@ -36,6 +38,10 @@ class BuiltinDeserializer final
// lazily deserialized at runtime.
Code* DeserializeBuiltin(int builtin_id);
// Deserializes the single given handler. This is used whenever a handler is
// lazily deserialized at runtime.
Code* DeserializeHandler(Bytecode bytecode, OperandScale operand_scale);
private:
// Deserializes the single given builtin. Assumes that reservations have
// already been allocated.
......@@ -43,8 +49,7 @@ class BuiltinDeserializer final
// Deserializes the single given bytecode handler. Assumes that reservations
// have already been allocated.
Code* DeserializeHandlerRaw(interpreter::Bytecode bytecode,
interpreter::OperandScale operand_scale);
Code* DeserializeHandlerRaw(Bytecode bytecode, OperandScale operand_scale);
// Extracts the size builtin Code objects (baked into the snapshot).
uint32_t ExtractCodeObjectSize(int builtin_id);
......@@ -57,6 +62,9 @@ class BuiltinDeserializer final
int CurrentCodeObjectId() const { return current_code_object_id_; }
// Convenience function to grab the handler off the heap's strong root list.
Code* GetDeserializeLazyHandler(OperandScale operand_scale) const;
private:
// Stores the code object currently being deserialized. The
// {current_code_object_id} stores the index of the currently-deserialized
......
......@@ -45,6 +45,16 @@ void BuiltinSerializer::SerializeBuiltinsAndHandlers() {
isolate()->interpreter()->GetBytecodeHandler(bytecode, operand_scale));
});
STATIC_ASSERT(BSU::kFirstHandlerIndex + BSU::kNumberOfHandlers ==
BSU::kNumberOfCodeObjects);
// The DeserializeLazy handlers are serialized by the StartupSerializer
// during strong root iteration.
DCHECK(isolate()->heap()->deserialize_lazy_handler()->IsCode());
DCHECK(isolate()->heap()->deserialize_lazy_handler_wide()->IsCode());
DCHECK(isolate()->heap()->deserialize_lazy_handler_extra_wide()->IsCode());
// Pad with kNop since GetInt() might read too far.
Pad();
......@@ -74,7 +84,7 @@ void BuiltinSerializer::SerializeBuiltin(Code* code) {
}
void BuiltinSerializer::SerializeHandler(Code* code) {
DCHECK_EQ(Code::BYTECODE_HANDLER, code->kind());
DCHECK(ObjectIsBytecodeHandler(code));
ObjectSerializer object_serializer(this, code, &sink_, kPlain,
kStartOfObject);
object_serializer.Serialize();
......
......@@ -212,11 +212,12 @@ bool Serializer<AllocatorT>::SerializeBuiltinReference(
return true;
}
// static
template <class AllocatorT>
bool Serializer<AllocatorT>::ObjectIsBytecodeHandler(HeapObject* obj) {
bool Serializer<AllocatorT>::ObjectIsBytecodeHandler(HeapObject* obj) const {
if (!obj->IsCode()) return false;
return (Code::cast(obj)->kind() == Code::BYTECODE_HANDLER);
Code* code = Code::cast(obj);
if (isolate()->heap()->IsDeserializeLazyHandler(code)) return false;
return (code->kind() == Code::BYTECODE_HANDLER);
}
template <class AllocatorT>
......
......@@ -195,7 +195,7 @@ class Serializer : public SerializerDeserializer {
int skip, BuiltinReferenceSerializationMode mode = kDefault);
// Returns true if the given heap object is a bytecode handler code object.
static bool ObjectIsBytecodeHandler(HeapObject* obj);
bool ObjectIsBytecodeHandler(HeapObject* obj) const;
inline void FlushSkip(int skip) {
if (skip != 0) {
......
......@@ -115,6 +115,36 @@ Code* Snapshot::DeserializeBuiltin(Isolate* isolate, int builtin_id) {
return code;
}
// static
Code* Snapshot::DeserializeHandler(Isolate* isolate,
interpreter::Bytecode bytecode,
interpreter::OperandScale operand_scale) {
base::ElapsedTimer timer;
if (FLAG_profile_deserialization) timer.Start();
const v8::StartupData* blob = isolate->snapshot_blob();
Vector<const byte> builtin_data = Snapshot::ExtractBuiltinData(blob);
BuiltinSnapshotData builtin_snapshot_data(builtin_data);
CodeSpaceMemoryModificationScope code_allocation(isolate->heap());
BuiltinDeserializer builtin_deserializer(isolate, &builtin_snapshot_data);
Code* code = builtin_deserializer.DeserializeHandler(bytecode, operand_scale);
if (FLAG_profile_deserialization) {
double ms = timer.Elapsed().InMillisecondsF();
int bytes = code->Size();
PrintF("[Deserializing handler %s (%d bytes) took %0.3f ms]\n",
interpreter::Bytecodes::ToString(bytecode, operand_scale).c_str(),
bytes, ms);
}
if (isolate->logger()->is_logging_code_events() || isolate->is_profiling()) {
isolate->logger()->LogCodeObject(code);
}
return code;
}
void ProfileDeserialization(
const SnapshotData* startup_snapshot, const SnapshotData* builtin_snapshot,
const std::vector<SnapshotData*>& context_snapshots) {
......
......@@ -98,6 +98,12 @@ class Snapshot : public AllStatic {
// initialized.
static Code* DeserializeBuiltin(Isolate* isolate, int builtin_id);
// Deserializes a single given handler code object. Intended to be called at
// runtime after the isolate has been fully initialized.
static Code* DeserializeHandler(Isolate* isolate,
interpreter::Bytecode bytecode,
interpreter::OperandScale operand_scale);
// ---------------- Helper methods ----------------
static bool HasContextSnapshot(Isolate* isolate, size_t index);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment