Handle<Object> p = it.rinfo()->target_object_handle(origin);
it.rinfo()->set_target_object(*p);
} else if (mode == RelocInfo::GLOBAL_PROPERTY_CELL) {
- Handle<JSGlobalPropertyCell> cell = it.rinfo()->target_cell_handle();
+ Handle<JSGlobalPropertyCell> cell = it.rinfo()->target_cell_handle();
it.rinfo()->set_target_cell(*cell);
} else if (RelocInfo::IsCodeTarget(mode)) {
// rewrite code handles in inline cache targets to direct
}
+int SafepointTableBuilder::CountShortDeoptimizationIntervals(unsigned limit) {
+ int result = 0;
+ if (!deoptimization_info_.is_empty()) {
+ unsigned previous_gap_end = deoptimization_info_[0].pc_after_gap;
+ for (int i = 1, n = deoptimization_info_.length(); i < n; i++) {
+ DeoptimizationInfo info = deoptimization_info_[i];
+ if (static_cast<int>(info.deoptimization_index) !=
+ Safepoint::kNoDeoptimizationIndex) {
+ if (previous_gap_end + limit > info.pc) {
+ result++;
+ }
+ previous_gap_end = info.pc_after_gap;
+ }
+ }
+ }
+ return result;
+}
+
+
+
} } // namespace v8::internal
int arguments,
int deoptimization_index);
- // Update the last safepoint with the size of the code generated for the gap
- // following it.
+ // Update the last safepoint with the size of the code generated until the
+ // end of the gap following it.
void SetPcAfterGap(int pc) {
ASSERT(!deoptimization_info_.is_empty());
int index = deoptimization_info_.length() - 1;
// entry must be enough to hold all the pointer indexes.
void Emit(Assembler* assembler, int bits_per_entry);
+ // Count the number of deoptimization points where the next
+ // following deoptimization point comes less than limit bytes
+ // after the end of this point's gap.
+ int CountShortDeoptimizationIntervals(unsigned limit);
+
private:
struct DeoptimizationInfo {
unsigned pc;
ZoneList<ZoneList<int>*> indexes_;
ZoneList<ZoneList<int>*> registers_;
- bool emitted_;
unsigned offset_;
+ bool emitted_;
DISALLOW_COPY_AND_ASSIGN(SafepointTableBuilder);
};
}
+// Calls directly to the given address using a relative offset.
+// Should only ever be used in Code objects for calls within the
+// same Code object. Should not be used when generating new code (use labels),
+// but only when patching existing code.
+void Assembler::call(Address target) {
+ positions_recorder()->WriteRecordedPositions();
+ EnsureSpace ensure_space(this);
+ last_pc_ = pc_;
+ // 1110 1000 #32-bit disp.
+ emit(0xE8);
+ Address source = pc_ + 4;
+ intptr_t displacement = target - source;
+ ASSERT(is_int32(displacement));
+ emitl(static_cast<int32_t>(displacement));
+}
+
+
void Assembler::clc() {
EnsureSpace ensure_space(this);
last_pc_ = pc_;
// TODO(X64): Rename this, removing the "Real", after changing the above.
static const int kRealPatchReturnSequenceAddressOffset = 2;
- // The x64 JS return sequence is padded with int3 to make it large
- // enough to hold a call instruction when the debugger patches it.
+ // Some x64 JS code is padded with int3 to make it large
+ // enough to hold an instruction when the debugger patches it.
+ static const int kJumpInstructionLength = 13;
static const int kCallInstructionLength = 13;
static const int kJSReturnSequenceLength = 13;
+ static const int kShortCallInstructionLength = 5;
// The debug break slot must be able to contain a call instruction.
static const int kDebugBreakSlotLength = kCallInstructionLength;
// Insert the smallest number of nop instructions
// possible to align the pc offset to a multiple
- // of m. m must be a power of 2.
+ // of m, where m must be a power of 2.
void Align(int m);
// Aligns code to something that's optimal for a jump target for the platform.
void CodeTargetAlign();
void call(Label* L);
void call(Handle<Code> target, RelocInfo::Mode rmode);
+ // Calls directly to the given address using a relative offset.
+ // Should only ever be used in Code objects for calls within the
+ // same Code object. Should not be used when generating new code (use labels),
+ // but only when patching existing code.
+ void call(Address target);
+
// Call near absolute indirect, address in register
void call(Register adr);
int Deoptimizer::patch_size() {
- return Assembler::kCallInstructionLength;
+ return MacroAssembler::kCallInstructionLength;
}
+#ifdef DEBUG
+// Overwrites code with int3 instructions.
+static void ZapCodeRange(Address from, Address to) {
+ CHECK(from <= to);
+ int length = static_cast<int>(to - from);
+ CodePatcher destroyer(from, length);
+ while (length-- > 0) {
+ destroyer.masm()->int3();
+ }
+}
+#endif
+
+
+// Iterate through the entries of a SafepointTable that corresponds to
+// deoptimization points.
+class SafepointTableDeoptimiztionEntryIterator {
+ public:
+ explicit SafepointTableDeoptimiztionEntryIterator(Code* code)
+ : code_(code), table_(code), index_(-1), limit_(table_.length()) {
+ FindNextIndex();
+ }
+
+ SafepointEntry Next(Address* pc) {
+ if (index_ >= limit_) {
+ *pc = NULL;
+ return SafepointEntry(); // Invalid entry.
+ }
+ *pc = code_->instruction_start() + table_.GetPcOffset(index_);
+ SafepointEntry entry = table_.GetEntry(index_);
+ FindNextIndex();
+ return entry;
+ }
+
+ private:
+ void FindNextIndex() {
+ ASSERT(index_ < limit_);
+ while (++index_ < limit_) {
+ if (table_.GetEntry(index_).deoptimization_index() !=
+ Safepoint::kNoDeoptimizationIndex) {
+ return;
+ }
+ }
+ }
+
+ Code* code_;
+ SafepointTable table_;
+ // Index of next deoptimization entry. If negative after calling
+ // FindNextIndex, there are no more, and Next will return an invalid
+ // SafepointEntry.
+ int index_;
+ // Table length.
+ int limit_;
+};
+
+
void Deoptimizer::DeoptimizeFunction(JSFunction* function) {
AssertNoAllocation no_allocation;
code->InvalidateRelocation();
// For each return after a safepoint insert a absolute call to the
- // corresponding deoptimization entry.
- unsigned last_pc_offset = 0;
- SafepointTable table(function->code());
- for (unsigned i = 0; i < table.length(); i++) {
- unsigned pc_offset = table.GetPcOffset(i);
- SafepointEntry safepoint_entry = table.GetEntry(i);
- int deoptimization_index = safepoint_entry.deoptimization_index();
- int gap_code_size = safepoint_entry.gap_code_size();
+ // corresponding deoptimization entry, or a short call to an absolute
+ // jump if space is short. The absolute jumps are put in a table just
+ // before the safepoint table (space was allocated there when the Code
+ // object was created, if necessary).
+
+ Address instruction_start = function->code()->instruction_start();
+ Address jump_table_address =
+ instruction_start + function->code()->safepoint_table_offset();
+ Address previous_pc = instruction_start;
+
+ SafepointTableDeoptimiztionEntryIterator deoptimizations(function->code());
+ Address entry_pc = NULL;
+
+ SafepointEntry current_entry = deoptimizations.Next(&entry_pc);
+ while (current_entry.is_valid()) {
+ int gap_code_size = current_entry.gap_code_size();
+ unsigned deoptimization_index = current_entry.deoptimization_index();
+
#ifdef DEBUG
// Destroy the code which is not supposed to run again.
- unsigned instructions = pc_offset - last_pc_offset;
- CodePatcher destroyer(code->instruction_start() + last_pc_offset,
- instructions);
- for (unsigned i = 0; i < instructions; i++) {
- destroyer.masm()->int3();
- }
+ ZapCodeRange(previous_pc, entry_pc);
#endif
- last_pc_offset = pc_offset;
- if (deoptimization_index != Safepoint::kNoDeoptimizationIndex) {
- last_pc_offset += gap_code_size;
- CodePatcher patcher(code->instruction_start() + last_pc_offset,
- patch_size());
+ // Position where Call will be patched in.
+ Address call_address = entry_pc + gap_code_size;
+ // End of call instruction, if using a direct call to a 64-bit address.
+ Address call_end_address =
+ call_address + MacroAssembler::kCallInstructionLength;
+
+ // Find next deoptimization entry, if any.
+ Address next_pc = NULL;
+ SafepointEntry next_entry = deoptimizations.Next(&next_pc);
+
+ if (!next_entry.is_valid() || next_pc >= call_end_address) {
+ // Room enough to write a long call instruction.
+ CodePatcher patcher(call_address, Assembler::kCallInstructionLength);
patcher.masm()->Call(GetDeoptimizationEntry(deoptimization_index, LAZY),
RelocInfo::NONE);
- last_pc_offset += patch_size();
+ previous_pc = call_end_address;
+ } else {
+ // Not room enough for a long Call instruction. Write a short call
+ // instruction to a long jump placed elsewhere in the code.
+ Address short_call_end_address =
+ call_address + MacroAssembler::kShortCallInstructionLength;
+ ASSERT(next_pc >= short_call_end_address);
+
+ // Write jump in jump-table.
+ jump_table_address -= MacroAssembler::kJumpInstructionLength;
+ CodePatcher jump_patcher(jump_table_address,
+ MacroAssembler::kJumpInstructionLength);
+ jump_patcher.masm()->Jump(
+ GetDeoptimizationEntry(deoptimization_index, LAZY),
+ RelocInfo::NONE);
+
+ // Write call to jump at call_offset.
+ CodePatcher call_patcher(call_address,
+ MacroAssembler::kShortCallInstructionLength);
+ call_patcher.masm()->call(jump_table_address);
+ previous_pc = short_call_end_address;
}
+
+ // Continue with next deoptimization entry.
+ current_entry = next_entry;
+ entry_pc = next_pc;
}
+
#ifdef DEBUG
// Destroy the code which is not supposed to run again.
- CHECK(code->safepoint_table_offset() >= last_pc_offset);
- unsigned instructions = code->safepoint_table_offset() - last_pc_offset;
- CodePatcher destroyer(code->instruction_start() + last_pc_offset,
- instructions);
- for (unsigned i = 0; i < instructions; i++) {
- destroyer.masm()->int3();
- }
+ ZapCodeRange(previous_pc, jump_table_address);
#endif
// Add the deoptimizing code to the list.
__ pop(Operand(rbx, offset));
}
- // Fill in the double input registers.
+ // Fill in the double input registers.
int double_regs_offset = FrameDescription::double_registers_offset();
for (int i = 0; i < XMMRegister::kNumAllocatableRegisters; i++) {
int dst_offset = i * kDoubleSize + double_regs_offset;
__ addq(rsp, Immediate(2 * kPointerSize));
}
- // Compute a pointer to the unwinding limit in register ecx; that is
+ // Compute a pointer to the unwinding limit in register rcx; that is
// the first stack slot not part of the input frame.
__ movq(rcx, Operand(rbx, FrameDescription::frame_size_offset()));
__ addq(rcx, rsp);
namespace v8 {
namespace internal {
-static const int kNumRegs = 8;
+static const int kNumRegs = 16;
static const RegList kJSCallerSaved =
1 << 0 | // rax
1 << 1 | // rcx
typedef Object* JSCallerSavedBuffer[kNumJSCallerSaved];
// Number of registers for which space is reserved in safepoints.
-// TODO(x64): This should not be 0.
-static const int kNumSafepointRegisters = 8;
+static const int kNumSafepointRegisters = 16;
// ----------------------------------------------------
bool LCodeGen::GenerateSafepointTable() {
ASSERT(is_done());
- // Ensure that patching a deoptimization point won't overwrite the table.
- for (int i = 0; i < Assembler::kCallInstructionLength; i++) {
- masm()->int3();
+ // Ensure that there is space at the end of the code to write a number
+ // of jump instructions, as well as to afford writing a call near the end
+ // of the code.
+ // The jumps are used when there isn't room in the code stream to write
+ // a long call instruction. Instead it writes a shorter call to a
+ // jump instruction in the same code object.
+ // The calls are used when lazy deoptimizing a function and calls to a
+ // deoptimization function.
+ int short_deopts = safepoints_.CountShortDeoptimizationIntervals(
+ static_cast<unsigned>(MacroAssembler::kJumpInstructionLength));
+ int byte_count = (short_deopts) * MacroAssembler::kJumpInstructionLength;
+ while (byte_count-- > 0) {
+ __ int3();
}
safepoints_.Emit(masm(), StackSlotCount());
return !is_aborted();
int arguments,
int deoptimization_index) {
const ZoneList<LOperand*>* operands = pointers->operands();
+
Safepoint safepoint = safepoints_.DefineSafepoint(masm(),
kind, arguments, deoptimization_index);
for (int i = 0; i < operands->length(); i++) {
void LCodeGen::DoJSArrayLength(LJSArrayLength* instr) {
- Abort("Unimplemented: %s", "DoJSArrayLength");
+ Register result = ToRegister(instr->result());
+ Register array = ToRegister(instr->InputAt(0));
+ __ movq(result, FieldOperand(array, JSArray::kLengthOffset));
}
void LCodeGen::DoLoadGlobal(LLoadGlobal* instr) {
- Register result = ToRegister(instr->result());
- if (result.is(rax)) {
- __ load_rax(instr->hydrogen()->cell().location(),
- RelocInfo::GLOBAL_PROPERTY_CELL);
- } else {
- __ movq(result, instr->hydrogen()->cell(), RelocInfo::GLOBAL_PROPERTY_CELL);
- __ movq(result, Operand(result, 0));
- }
- if (instr->hydrogen()->check_hole_value()) {
- __ CompareRoot(result, Heap::kTheHoleValueRootIndex);
- DeoptimizeIf(equal, instr->environment());
- }
+ Abort("Unimplemented: %s", "DoLoadGlobal");
}
void LCodeGen::DoStoreGlobal(LStoreGlobal* instr) {
Register value = ToRegister(instr->InputAt(0));
- if (value.is(rax)) {
+ Register temp = ToRegister(instr->TempAt(0));
+ ASSERT(!value.is(temp));
+ bool check_hole = instr->hydrogen()->check_hole_value();
+ if (!check_hole && value.is(rax)) {
__ store_rax(instr->hydrogen()->cell().location(),
RelocInfo::GLOBAL_PROPERTY_CELL);
- } else {
- __ movq(kScratchRegister,
- Handle<Object>::cast(instr->hydrogen()->cell()),
- RelocInfo::GLOBAL_PROPERTY_CELL);
- __ movq(Operand(kScratchRegister, 0), value);
+ return;
}
+ // If the cell we are storing to contains the hole it could have
+ // been deleted from the property dictionary. In that case, we need
+ // to update the property details in the property dictionary to mark
+ // it as no longer deleted. We deoptimize in that case.
+ __ movq(temp,
+ Handle<Object>::cast(instr->hydrogen()->cell()),
+ RelocInfo::GLOBAL_PROPERTY_CELL);
+ if (check_hole) {
+ __ CompareRoot(Operand(temp, 0), Heap::kTheHoleValueRootIndex);
+ DeoptimizeIf(equal, instr->environment());
+ }
+ __ movq(Operand(temp, 0), value);
}
void LCodeGen::DoCallKnownGlobal(LCallKnownGlobal* instr) {
- Abort("Unimplemented: %s", "DoCallKnownGlobal");
+ ASSERT(ToRegister(instr->result()).is(rax));
+ __ Move(rdi, instr->target());
+ CallKnownFunction(instr->target(), instr->arity(), instr);
}
void LCodeGen::DoCheckInstanceType(LCheckInstanceType* instr) {
- Abort("Unimplemented: %s", "DoCheckInstanceType");
+ Register input = ToRegister(instr->InputAt(0));
+ InstanceType first = instr->hydrogen()->first();
+ InstanceType last = instr->hydrogen()->last();
+
+ __ movq(kScratchRegister, FieldOperand(input, HeapObject::kMapOffset));
+
+ // If there is only one type in the interval check for equality.
+ if (first == last) {
+ __ cmpb(FieldOperand(kScratchRegister, Map::kInstanceTypeOffset),
+ Immediate(static_cast<int8_t>(first)));
+ DeoptimizeIf(not_equal, instr->environment());
+ } else if (first == FIRST_STRING_TYPE && last == LAST_STRING_TYPE) {
+ // String has a dedicated bit in instance type.
+ __ testb(FieldOperand(kScratchRegister, Map::kInstanceTypeOffset),
+ Immediate(kIsNotStringMask));
+ DeoptimizeIf(not_zero, instr->environment());
+ } else {
+ __ cmpb(FieldOperand(kScratchRegister, Map::kInstanceTypeOffset),
+ Immediate(static_cast<int8_t>(first)));
+ DeoptimizeIf(below, instr->environment());
+ // Omit check for the last type.
+ if (last != LAST_TYPE) {
+ __ cmpb(FieldOperand(kScratchRegister, Map::kInstanceTypeOffset),
+ Immediate(static_cast<int8_t>(last)));
+ DeoptimizeIf(above, instr->environment());
+ }
+ }
}
LInstruction* LChunkBuilder::DoCallNamed(HCallNamed* instr) {
- Abort("Unimplemented: %s", "DoCallNamed");
- return NULL;
+ argument_count_ -= instr->argument_count();
+ return MarkAsCall(DefineFixed(new LCallNamed, rax), instr);
}
LInstruction* LChunkBuilder::DoCallKnownGlobal(HCallKnownGlobal* instr) {
- Abort("Unimplemented: %s", "DoCallKnownGlobal");
- return NULL;
+ argument_count_ -= instr->argument_count();
+ return MarkAsCall(DefineFixed(new LCallKnownGlobal, rax), instr);
}
LInstruction* LChunkBuilder::DoJSArrayLength(HJSArrayLength* instr) {
- Abort("Unimplemented: %s", "DoJSArrayLength");
- return NULL;
+ LOperand* array = UseRegisterAtStart(instr->value());
+ return DefineAsRegister(new LJSArrayLength(array));
}
LInstruction* LChunkBuilder::DoCheckInstanceType(HCheckInstanceType* instr) {
- Abort("Unimplemented: %s", "DoCheckInstanceType");
- return NULL;
+ LOperand* value = UseRegisterAtStart(instr->value());
+ LCheckInstanceType* result = new LCheckInstanceType(value);
+ return AssignEnvironment(result);
}
LInstruction* LChunkBuilder::DoStoreGlobal(HStoreGlobal* instr) {
- return new LStoreGlobal(UseRegisterAtStart(instr->value()));
+ LStoreGlobal* result = new LStoreGlobal(UseRegister(instr->value()),
+ TempRegister());
+ return instr->check_hole_value() ? AssignEnvironment(result) : result;
}
};
-class LStoreGlobal: public LTemplateInstruction<0, 1, 0> {
+class LStoreGlobal: public LTemplateInstruction<0, 1, 1> {
public:
- explicit LStoreGlobal(LOperand* value) {
+ explicit LStoreGlobal(LOperand* value, LOperand* temp) {
inputs_[0] = value;
+ temps_[0] = temp;
}
DECLARE_CONCRETE_INSTRUCTION(StoreGlobal, "store-global")
};
-class LCheckInstanceType: public LTemplateInstruction<0, 1, 1> {
+class LCheckInstanceType: public LTemplateInstruction<0, 1, 0> {
public:
- LCheckInstanceType(LOperand* value, LOperand* temp) {
+ explicit LCheckInstanceType(LOperand* value) {
inputs_[0] = value;
- temps_[0] = temp;
}
DECLARE_CONCRETE_INSTRUCTION(CheckInstanceType, "check-instance-type")
}
-void MacroAssembler::CompareRoot(Operand with, Heap::RootListIndex index) {
+void MacroAssembler::CompareRoot(const Operand& with,
+ Heap::RootListIndex index) {
+ ASSERT(!with.AddressUsesRegister(kScratchRegister));
LoadRoot(kScratchRegister, index);
cmpq(with, kScratchRegister);
}
void LoadRoot(Register destination, Heap::RootListIndex index);
void CompareRoot(Register with, Heap::RootListIndex index);
- void CompareRoot(Operand with, Heap::RootListIndex index);
+ void CompareRoot(const Operand& with, Heap::RootListIndex index);
void PushRoot(Heap::RootListIndex index);
void StoreRoot(Register source, Heap::RootListIndex index);
// Emit call to the code we are currently generating.
void CallSelf() {
Handle<Code> self(reinterpret_cast<Code**>(CodeObject().location()));
- call(self, RelocInfo::CODE_TARGET);
+ Call(self, RelocInfo::CODE_TARGET);
}
// Non-x64 instructions.
test-deoptimization/DeoptimizeBinaryOperationDIV: FAIL
test-deoptimization/DeoptimizeLoadICStoreIC: FAIL
test-deoptimization/DeoptimizeLoadICStoreICNested: FAIL
-test-deoptimization/DeoptimizeCompare: FAIL
+test-deoptimization/DeoptimizeCompare: PASS || FAIL
# Tests that time out with crankshaft.
test-api/Threading: SKIP