Revert "Revert "Upgrade to 5.0.71.48"" DO NOT MERGE
This reverts commit f2e3994fa5148cc3d9946666f0b0596290192b0e,
and updates the x64 makefile properly so it doesn't break that
build.
FPIIM-449
Change-Id: Ib83e35bfbae6af627451c926a9650ec57c045605
(cherry picked from commit 109988c7ccb6f3fd1a58574fa3dfb88beaef6632)
diff --git a/src/interpreter/interpreter-assembler.cc b/src/interpreter/interpreter-assembler.cc
new file mode 100644
index 0000000..440e879
--- /dev/null
+++ b/src/interpreter/interpreter-assembler.cc
@@ -0,0 +1,546 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "src/interpreter/interpreter-assembler.h"
+
+#include <ostream>
+
+#include "src/code-factory.h"
+#include "src/frames.h"
+#include "src/interface-descriptors.h"
+#include "src/interpreter/bytecodes.h"
+#include "src/interpreter/interpreter.h"
+#include "src/machine-type.h"
+#include "src/macro-assembler.h"
+#include "src/zone.h"
+
+namespace v8 {
+namespace internal {
+namespace interpreter {
+
+using compiler::Node;
+
+InterpreterAssembler::InterpreterAssembler(Isolate* isolate, Zone* zone,
+ Bytecode bytecode)
+ : compiler::CodeStubAssembler(
+ isolate, zone, InterpreterDispatchDescriptor(isolate),
+ Code::ComputeFlags(Code::STUB), Bytecodes::ToString(bytecode), 0),
+ bytecode_(bytecode),
+ accumulator_(this, MachineRepresentation::kTagged),
+ context_(this, MachineRepresentation::kTagged),
+ bytecode_array_(this, MachineRepresentation::kTagged),
+ disable_stack_check_across_call_(false),
+ stack_pointer_before_call_(nullptr) {
+ accumulator_.Bind(
+ Parameter(InterpreterDispatchDescriptor::kAccumulatorParameter));
+ context_.Bind(Parameter(InterpreterDispatchDescriptor::kContextParameter));
+ bytecode_array_.Bind(
+ Parameter(InterpreterDispatchDescriptor::kBytecodeArrayParameter));
+ if (FLAG_trace_ignition) {
+ TraceBytecode(Runtime::kInterpreterTraceBytecodeEntry);
+ }
+}
+
+InterpreterAssembler::~InterpreterAssembler() {}
+
+Node* InterpreterAssembler::GetAccumulator() { return accumulator_.value(); }
+
+void InterpreterAssembler::SetAccumulator(Node* value) {
+ accumulator_.Bind(value);
+}
+
+Node* InterpreterAssembler::GetContext() { return context_.value(); }
+
+void InterpreterAssembler::SetContext(Node* value) {
+ StoreRegister(value, Register::current_context());
+ context_.Bind(value);
+}
+
+Node* InterpreterAssembler::BytecodeOffset() {
+ return Parameter(InterpreterDispatchDescriptor::kBytecodeOffsetParameter);
+}
+
+Node* InterpreterAssembler::RegisterFileRawPointer() {
+ return Parameter(InterpreterDispatchDescriptor::kRegisterFileParameter);
+}
+
+Node* InterpreterAssembler::BytecodeArrayTaggedPointer() {
+ return bytecode_array_.value();
+}
+
+Node* InterpreterAssembler::DispatchTableRawPointer() {
+ return Parameter(InterpreterDispatchDescriptor::kDispatchTableParameter);
+}
+
+Node* InterpreterAssembler::RegisterLocation(Node* reg_index) {
+ return IntPtrAdd(RegisterFileRawPointer(), RegisterFrameOffset(reg_index));
+}
+
+Node* InterpreterAssembler::LoadRegister(int offset) {
+ return Load(MachineType::AnyTagged(), RegisterFileRawPointer(),
+ Int32Constant(offset));
+}
+
+Node* InterpreterAssembler::LoadRegister(Register reg) {
+ return LoadRegister(reg.ToOperand() << kPointerSizeLog2);
+}
+
+Node* InterpreterAssembler::RegisterFrameOffset(Node* index) {
+ return WordShl(index, kPointerSizeLog2);
+}
+
+Node* InterpreterAssembler::LoadRegister(Node* reg_index) {
+ return Load(MachineType::AnyTagged(), RegisterFileRawPointer(),
+ RegisterFrameOffset(reg_index));
+}
+
+Node* InterpreterAssembler::StoreRegister(Node* value, int offset) {
+ return StoreNoWriteBarrier(MachineRepresentation::kTagged,
+ RegisterFileRawPointer(), Int32Constant(offset),
+ value);
+}
+
+Node* InterpreterAssembler::StoreRegister(Node* value, Register reg) {
+ return StoreRegister(value, reg.ToOperand() << kPointerSizeLog2);
+}
+
+Node* InterpreterAssembler::StoreRegister(Node* value, Node* reg_index) {
+ return StoreNoWriteBarrier(MachineRepresentation::kTagged,
+ RegisterFileRawPointer(),
+ RegisterFrameOffset(reg_index), value);
+}
+
+Node* InterpreterAssembler::NextRegister(Node* reg_index) {
+ // Register indexes are negative, so the next index is minus one.
+ return IntPtrAdd(reg_index, Int32Constant(-1));
+}
+
+Node* InterpreterAssembler::BytecodeOperand(int operand_index) {
+ DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_));
+ DCHECK_EQ(OperandSize::kByte,
+ Bytecodes::GetOperandSize(bytecode_, operand_index));
+ return Load(
+ MachineType::Uint8(), BytecodeArrayTaggedPointer(),
+ IntPtrAdd(BytecodeOffset(), Int32Constant(Bytecodes::GetOperandOffset(
+ bytecode_, operand_index))));
+}
+
+Node* InterpreterAssembler::BytecodeOperandSignExtended(int operand_index) {
+ DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_));
+ DCHECK_EQ(OperandSize::kByte,
+ Bytecodes::GetOperandSize(bytecode_, operand_index));
+ Node* load = Load(
+ MachineType::Int8(), BytecodeArrayTaggedPointer(),
+ IntPtrAdd(BytecodeOffset(), Int32Constant(Bytecodes::GetOperandOffset(
+ bytecode_, operand_index))));
+ // Ensure that we sign extend to full pointer size
+ if (kPointerSize == 8) {
+ load = ChangeInt32ToInt64(load);
+ }
+ return load;
+}
+
+Node* InterpreterAssembler::BytecodeOperandShort(int operand_index) {
+ DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_));
+ DCHECK_EQ(OperandSize::kShort,
+ Bytecodes::GetOperandSize(bytecode_, operand_index));
+ if (TargetSupportsUnalignedAccess()) {
+ return Load(
+ MachineType::Uint16(), BytecodeArrayTaggedPointer(),
+ IntPtrAdd(BytecodeOffset(), Int32Constant(Bytecodes::GetOperandOffset(
+ bytecode_, operand_index))));
+ } else {
+ int offset = Bytecodes::GetOperandOffset(bytecode_, operand_index);
+ Node* first_byte = Load(MachineType::Uint8(), BytecodeArrayTaggedPointer(),
+ IntPtrAdd(BytecodeOffset(), Int32Constant(offset)));
+ Node* second_byte =
+ Load(MachineType::Uint8(), BytecodeArrayTaggedPointer(),
+ IntPtrAdd(BytecodeOffset(), Int32Constant(offset + 1)));
+#if V8_TARGET_LITTLE_ENDIAN
+ return WordOr(WordShl(second_byte, kBitsPerByte), first_byte);
+#elif V8_TARGET_BIG_ENDIAN
+ return WordOr(WordShl(first_byte, kBitsPerByte), second_byte);
+#else
+#error "Unknown Architecture"
+#endif
+ }
+}
+
+Node* InterpreterAssembler::BytecodeOperandShortSignExtended(
+ int operand_index) {
+ DCHECK_LT(operand_index, Bytecodes::NumberOfOperands(bytecode_));
+ DCHECK_EQ(OperandSize::kShort,
+ Bytecodes::GetOperandSize(bytecode_, operand_index));
+ int operand_offset = Bytecodes::GetOperandOffset(bytecode_, operand_index);
+ Node* load;
+ if (TargetSupportsUnalignedAccess()) {
+ load = Load(MachineType::Int16(), BytecodeArrayTaggedPointer(),
+ IntPtrAdd(BytecodeOffset(), Int32Constant(operand_offset)));
+ } else {
+#if V8_TARGET_LITTLE_ENDIAN
+ Node* hi_byte_offset = Int32Constant(operand_offset + 1);
+ Node* lo_byte_offset = Int32Constant(operand_offset);
+#elif V8_TARGET_BIG_ENDIAN
+ Node* hi_byte_offset = Int32Constant(operand_offset);
+ Node* lo_byte_offset = Int32Constant(operand_offset + 1);
+#else
+#error "Unknown Architecture"
+#endif
+ Node* hi_byte = Load(MachineType::Int8(), BytecodeArrayTaggedPointer(),
+ IntPtrAdd(BytecodeOffset(), hi_byte_offset));
+ Node* lo_byte = Load(MachineType::Uint8(), BytecodeArrayTaggedPointer(),
+ IntPtrAdd(BytecodeOffset(), lo_byte_offset));
+ hi_byte = Word32Shl(hi_byte, Int32Constant(kBitsPerByte));
+ load = Word32Or(hi_byte, lo_byte);
+ }
+
+ // Ensure that we sign extend to full pointer size
+ if (kPointerSize == 8) {
+ load = ChangeInt32ToInt64(load);
+ }
+ return load;
+}
+
+Node* InterpreterAssembler::BytecodeOperandCount(int operand_index) {
+ switch (Bytecodes::GetOperandSize(bytecode_, operand_index)) {
+ case OperandSize::kByte:
+ DCHECK_EQ(OperandType::kRegCount8,
+ Bytecodes::GetOperandType(bytecode_, operand_index));
+ return BytecodeOperand(operand_index);
+ case OperandSize::kShort:
+ DCHECK_EQ(OperandType::kRegCount16,
+ Bytecodes::GetOperandType(bytecode_, operand_index));
+ return BytecodeOperandShort(operand_index);
+ case OperandSize::kNone:
+ UNREACHABLE();
+ }
+ return nullptr;
+}
+
+Node* InterpreterAssembler::BytecodeOperandImm(int operand_index) {
+ DCHECK_EQ(OperandType::kImm8,
+ Bytecodes::GetOperandType(bytecode_, operand_index));
+ return BytecodeOperandSignExtended(operand_index);
+}
+
+Node* InterpreterAssembler::BytecodeOperandIdx(int operand_index) {
+ switch (Bytecodes::GetOperandSize(bytecode_, operand_index)) {
+ case OperandSize::kByte:
+ DCHECK_EQ(OperandType::kIdx8,
+ Bytecodes::GetOperandType(bytecode_, operand_index));
+ return BytecodeOperand(operand_index);
+ case OperandSize::kShort:
+ DCHECK_EQ(OperandType::kIdx16,
+ Bytecodes::GetOperandType(bytecode_, operand_index));
+ return BytecodeOperandShort(operand_index);
+ case OperandSize::kNone:
+ UNREACHABLE();
+ }
+ return nullptr;
+}
+
+Node* InterpreterAssembler::BytecodeOperandReg(int operand_index) {
+ OperandType operand_type =
+ Bytecodes::GetOperandType(bytecode_, operand_index);
+ if (Bytecodes::IsRegisterOperandType(operand_type)) {
+ OperandSize operand_size = Bytecodes::SizeOfOperand(operand_type);
+ if (operand_size == OperandSize::kByte) {
+ return BytecodeOperandSignExtended(operand_index);
+ } else if (operand_size == OperandSize::kShort) {
+ return BytecodeOperandShortSignExtended(operand_index);
+ }
+ }
+ UNREACHABLE();
+ return nullptr;
+}
+
+Node* InterpreterAssembler::LoadConstantPoolEntry(Node* index) {
+ Node* constant_pool = LoadObjectField(BytecodeArrayTaggedPointer(),
+ BytecodeArray::kConstantPoolOffset);
+ Node* entry_offset =
+ IntPtrAdd(IntPtrConstant(FixedArray::kHeaderSize - kHeapObjectTag),
+ WordShl(index, kPointerSizeLog2));
+ return Load(MachineType::AnyTagged(), constant_pool, entry_offset);
+}
+
+Node* InterpreterAssembler::LoadFixedArrayElement(Node* fixed_array,
+ int index) {
+ Node* entry_offset =
+ IntPtrAdd(IntPtrConstant(FixedArray::kHeaderSize - kHeapObjectTag),
+ WordShl(Int32Constant(index), kPointerSizeLog2));
+ return Load(MachineType::AnyTagged(), fixed_array, entry_offset);
+}
+
+Node* InterpreterAssembler::LoadObjectField(Node* object, int offset) {
+ return Load(MachineType::AnyTagged(), object,
+ IntPtrConstant(offset - kHeapObjectTag));
+}
+
+Node* InterpreterAssembler::LoadContextSlot(Node* context, int slot_index) {
+ return Load(MachineType::AnyTagged(), context,
+ IntPtrConstant(Context::SlotOffset(slot_index)));
+}
+
+Node* InterpreterAssembler::LoadContextSlot(Node* context, Node* slot_index) {
+ Node* offset =
+ IntPtrAdd(WordShl(slot_index, kPointerSizeLog2),
+ Int32Constant(Context::kHeaderSize - kHeapObjectTag));
+ return Load(MachineType::AnyTagged(), context, offset);
+}
+
+Node* InterpreterAssembler::StoreContextSlot(Node* context, Node* slot_index,
+ Node* value) {
+ Node* offset =
+ IntPtrAdd(WordShl(slot_index, kPointerSizeLog2),
+ Int32Constant(Context::kHeaderSize - kHeapObjectTag));
+ return Store(MachineRepresentation::kTagged, context, offset, value);
+}
+
+Node* InterpreterAssembler::LoadTypeFeedbackVector() {
+ Node* function = Load(
+ MachineType::AnyTagged(), RegisterFileRawPointer(),
+ IntPtrConstant(InterpreterFrameConstants::kFunctionFromRegisterPointer));
+ Node* shared_info =
+ LoadObjectField(function, JSFunction::kSharedFunctionInfoOffset);
+ Node* vector =
+ LoadObjectField(shared_info, SharedFunctionInfo::kFeedbackVectorOffset);
+ return vector;
+}
+
+void InterpreterAssembler::CallPrologue() {
+ StoreRegister(SmiTag(BytecodeOffset()),
+ InterpreterFrameConstants::kBytecodeOffsetFromRegisterPointer);
+ StoreRegister(BytecodeArrayTaggedPointer(),
+ InterpreterFrameConstants::kBytecodeArrayFromRegisterPointer);
+
+ if (FLAG_debug_code && !disable_stack_check_across_call_) {
+ DCHECK(stack_pointer_before_call_ == nullptr);
+ stack_pointer_before_call_ = LoadStackPointer();
+ }
+}
+
+void InterpreterAssembler::CallEpilogue() {
+ if (FLAG_debug_code && !disable_stack_check_across_call_) {
+ Node* stack_pointer_after_call = LoadStackPointer();
+ Node* stack_pointer_before_call = stack_pointer_before_call_;
+ stack_pointer_before_call_ = nullptr;
+ AbortIfWordNotEqual(stack_pointer_before_call, stack_pointer_after_call,
+ kUnexpectedStackPointer);
+ }
+
+ // Restore bytecode array from stack frame in case the debugger has swapped us
+ // to the patched debugger bytecode array.
+ bytecode_array_.Bind(LoadRegister(
+ InterpreterFrameConstants::kBytecodeArrayFromRegisterPointer));
+}
+
+Node* InterpreterAssembler::CallJS(Node* function, Node* context,
+ Node* first_arg, Node* arg_count,
+ TailCallMode tail_call_mode) {
+ Callable callable =
+ CodeFactory::InterpreterPushArgsAndCall(isolate(), tail_call_mode);
+ Node* code_target = HeapConstant(callable.code());
+ return CallStub(callable.descriptor(), code_target, context, arg_count,
+ first_arg, function);
+}
+
+Node* InterpreterAssembler::CallConstruct(Node* constructor, Node* context,
+ Node* new_target, Node* first_arg,
+ Node* arg_count) {
+ Callable callable = CodeFactory::InterpreterPushArgsAndConstruct(isolate());
+ Node* code_target = HeapConstant(callable.code());
+ return CallStub(callable.descriptor(), code_target, context, arg_count,
+ new_target, constructor, first_arg);
+}
+
+Node* InterpreterAssembler::CallRuntimeN(Node* function_id, Node* context,
+ Node* first_arg, Node* arg_count,
+ int result_size) {
+ Callable callable = CodeFactory::InterpreterCEntry(isolate(), result_size);
+ Node* code_target = HeapConstant(callable.code());
+
+ // Get the function entry from the function id.
+ Node* function_table = ExternalConstant(
+ ExternalReference::runtime_function_table_address(isolate()));
+ Node* function_offset =
+ Int32Mul(function_id, Int32Constant(sizeof(Runtime::Function)));
+ Node* function = IntPtrAdd(function_table, function_offset);
+ Node* function_entry =
+ Load(MachineType::Pointer(), function,
+ Int32Constant(offsetof(Runtime::Function, entry)));
+
+ return CallStub(callable.descriptor(), code_target, context, arg_count,
+ first_arg, function_entry, result_size);
+}
+
+void InterpreterAssembler::UpdateInterruptBudget(Node* weight) {
+ CodeStubAssembler::Label ok(this);
+ CodeStubAssembler::Label interrupt_check(this);
+ CodeStubAssembler::Label end(this);
+ Node* budget_offset =
+ IntPtrConstant(BytecodeArray::kInterruptBudgetOffset - kHeapObjectTag);
+
+ // Update budget by |weight| and check if it reaches zero.
+ Node* old_budget =
+ Load(MachineType::Int32(), BytecodeArrayTaggedPointer(), budget_offset);
+ Node* new_budget = Int32Add(old_budget, weight);
+ Node* condition = Int32GreaterThanOrEqual(new_budget, Int32Constant(0));
+ Branch(condition, &ok, &interrupt_check);
+
+ // Perform interrupt and reset budget.
+ Bind(&interrupt_check);
+ CallRuntime(Runtime::kInterrupt, GetContext());
+ StoreNoWriteBarrier(MachineRepresentation::kWord32,
+ BytecodeArrayTaggedPointer(), budget_offset,
+ Int32Constant(Interpreter::InterruptBudget()));
+ Goto(&end);
+
+ // Update budget.
+ Bind(&ok);
+ StoreNoWriteBarrier(MachineRepresentation::kWord32,
+ BytecodeArrayTaggedPointer(), budget_offset, new_budget);
+ Goto(&end);
+ Bind(&end);
+}
+
+Node* InterpreterAssembler::Advance(int delta) {
+ return IntPtrAdd(BytecodeOffset(), Int32Constant(delta));
+}
+
+Node* InterpreterAssembler::Advance(Node* delta) {
+ return IntPtrAdd(BytecodeOffset(), delta);
+}
+
+void InterpreterAssembler::Jump(Node* delta) {
+ UpdateInterruptBudget(delta);
+ DispatchTo(Advance(delta));
+}
+
+void InterpreterAssembler::JumpConditional(Node* condition, Node* delta) {
+ CodeStubAssembler::Label match(this);
+ CodeStubAssembler::Label no_match(this);
+
+ Branch(condition, &match, &no_match);
+ Bind(&match);
+ Jump(delta);
+ Bind(&no_match);
+ Dispatch();
+}
+
+void InterpreterAssembler::JumpIfWordEqual(Node* lhs, Node* rhs, Node* delta) {
+ JumpConditional(WordEqual(lhs, rhs), delta);
+}
+
+void InterpreterAssembler::JumpIfWordNotEqual(Node* lhs, Node* rhs,
+ Node* delta) {
+ JumpConditional(WordNotEqual(lhs, rhs), delta);
+}
+
+void InterpreterAssembler::Dispatch() {
+ DispatchTo(Advance(Bytecodes::Size(bytecode_)));
+}
+
+void InterpreterAssembler::DispatchTo(Node* new_bytecode_offset) {
+ Node* target_bytecode = Load(
+ MachineType::Uint8(), BytecodeArrayTaggedPointer(), new_bytecode_offset);
+
+ // TODO(rmcilroy): Create a code target dispatch table to avoid conversion
+ // from code object on every dispatch.
+ Node* target_code_object =
+ Load(MachineType::Pointer(), DispatchTableRawPointer(),
+ Word32Shl(target_bytecode, Int32Constant(kPointerSizeLog2)));
+
+ DispatchToBytecodeHandler(target_code_object, new_bytecode_offset);
+}
+
+void InterpreterAssembler::DispatchToBytecodeHandler(Node* handler,
+ Node* bytecode_offset) {
+ if (FLAG_trace_ignition) {
+ TraceBytecode(Runtime::kInterpreterTraceBytecodeExit);
+ }
+
+ InterpreterDispatchDescriptor descriptor(isolate());
+ Node* args[] = {GetAccumulator(), RegisterFileRawPointer(),
+ bytecode_offset, BytecodeArrayTaggedPointer(),
+ DispatchTableRawPointer(), GetContext()};
+ TailCall(descriptor, handler, args, 0);
+}
+
+void InterpreterAssembler::InterpreterReturn() {
+ // TODO(rmcilroy): Investigate whether it is worth supporting self
+ // optimization of primitive functions like FullCodegen.
+
+ // Update profiling count by -BytecodeOffset to simulate backedge to start of
+ // function.
+ Node* profiling_weight =
+ Int32Sub(Int32Constant(kHeapObjectTag + BytecodeArray::kHeaderSize),
+ BytecodeOffset());
+ UpdateInterruptBudget(profiling_weight);
+
+ Node* exit_trampoline_code_object =
+ HeapConstant(isolate()->builtins()->InterpreterExitTrampoline());
+ DispatchToBytecodeHandler(exit_trampoline_code_object);
+}
+
+void InterpreterAssembler::StackCheck() {
+ CodeStubAssembler::Label end(this);
+ CodeStubAssembler::Label ok(this);
+ CodeStubAssembler::Label stack_guard(this);
+
+ Node* sp = LoadStackPointer();
+ Node* stack_limit = Load(
+ MachineType::Pointer(),
+ ExternalConstant(ExternalReference::address_of_stack_limit(isolate())));
+ Node* condition = UintPtrGreaterThanOrEqual(sp, stack_limit);
+ Branch(condition, &ok, &stack_guard);
+ Bind(&stack_guard);
+ CallRuntime(Runtime::kStackGuard, GetContext());
+ Goto(&end);
+ Bind(&ok);
+ Goto(&end);
+ Bind(&end);
+}
+
+void InterpreterAssembler::Abort(BailoutReason bailout_reason) {
+ disable_stack_check_across_call_ = true;
+ Node* abort_id = SmiTag(Int32Constant(bailout_reason));
+ Node* ret_value = CallRuntime(Runtime::kAbort, GetContext(), abort_id);
+ disable_stack_check_across_call_ = false;
+ // Unreached, but keeps turbofan happy.
+ Return(ret_value);
+}
+
+void InterpreterAssembler::AbortIfWordNotEqual(Node* lhs, Node* rhs,
+ BailoutReason bailout_reason) {
+ CodeStubAssembler::Label match(this);
+ CodeStubAssembler::Label no_match(this);
+
+ Node* condition = WordEqual(lhs, rhs);
+ Branch(condition, &match, &no_match);
+ Bind(&no_match);
+ Abort(bailout_reason);
+ Bind(&match);
+}
+
+void InterpreterAssembler::TraceBytecode(Runtime::FunctionId function_id) {
+ CallRuntime(function_id, GetContext(), BytecodeArrayTaggedPointer(),
+ SmiTag(BytecodeOffset()), GetAccumulator());
+}
+
+// static
+bool InterpreterAssembler::TargetSupportsUnalignedAccess() {
+#if V8_TARGET_ARCH_MIPS || V8_TARGET_ARCH_MIPS64
+ return false;
+#elif V8_TARGET_ARCH_ARM || V8_TARGET_ARCH_ARM64 || V8_TARGET_ARCH_PPC
+ return CpuFeatures::IsSupported(UNALIGNED_ACCESSES);
+#elif V8_TARGET_ARCH_IA32 || V8_TARGET_ARCH_X64 || V8_TARGET_ARCH_X87
+ return true;
+#else
+#error "Unknown Architecture"
+#endif
+}
+
+} // namespace interpreter
+} // namespace internal
+} // namespace v8