2010-04-26: Version 2.2.5
Various performance improvements (especially for ARM and x64)
Fixed bug in CPU profiling (http://crbug.com/42137)
Fixed a bug with the natives cache.
Fixed two bugs in the ARM code generator that can cause wrong calculations.
Fixed a bug that may cause a wrong result for shift operations.
git-svn-id: http://v8.googlecode.com/svn/trunk@4498 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
diff --git a/src/arm/assembler-arm.cc b/src/arm/assembler-arm.cc
index 23d5e00..a6d81bf 100644
--- a/src/arm/assembler-arm.cc
+++ b/src/arm/assembler-arm.cc
@@ -306,6 +306,7 @@
reloc_info_writer.Reposition(buffer_ + buffer_size, pc_);
num_prinfo_ = 0;
next_buffer_check_ = 0;
+ const_pool_blocked_nesting_ = 0;
no_const_pool_before_ = 0;
last_const_pool_end_ = 0;
last_bound_pos_ = 0;
@@ -317,6 +318,7 @@
Assembler::~Assembler() {
+ ASSERT(const_pool_blocked_nesting_ == 0);
if (own_buffer_) {
if (spare_buffer_ == NULL && buffer_size_ == kMinimalBufferSize) {
spare_buffer_ = buffer_;
@@ -348,6 +350,51 @@
}
+bool Assembler::IsNop(Instr instr, int type) {
+ // Check for mov rx, rx.
+ ASSERT(0 <= type && type <= 14); // mov pc, pc is not a nop.
+ return instr == (al | 13*B21 | type*B12 | type);
+}
+
+
+bool Assembler::IsBranch(Instr instr) {
+ return (instr & (B27 | B25)) == (B27 | B25);
+}
+
+
+int Assembler::GetBranchOffset(Instr instr) {
+ ASSERT(IsBranch(instr));
+ // Take the jump offset in the lower 24 bits, sign extend it and multiply it
+ // with 4 to get the offset in bytes.
+ return ((instr & Imm24Mask) << 8) >> 6;
+}
+
+
+bool Assembler::IsLdrRegisterImmediate(Instr instr) {
+ return (instr & (B27 | B26 | B25 | B22 | B20)) == (B26 | B20);
+}
+
+
+int Assembler::GetLdrRegisterImmediateOffset(Instr instr) {
+ ASSERT(IsLdrRegisterImmediate(instr));
+ bool positive = (instr & B23) == B23;
+ int offset = instr & Off12Mask; // Zero extended offset.
+ return positive ? offset : -offset;
+}
+
+
+Instr Assembler::SetLdrRegisterImmediateOffset(Instr instr, int offset) {
+ ASSERT(IsLdrRegisterImmediate(instr));
+ bool positive = offset >= 0;
+ if (!positive) offset = -offset;
+ ASSERT(is_uint12(offset));
+ // Set bit indicating whether the offset should be added.
+ instr = (instr & ~B23) | (positive ? B23 : 0);
+ // Set the actual offset.
+ return (instr & ~Off12Mask) | offset;
+}
+
+
// Labels refer to positions in the (to be) generated code.
// There are bound, linked, and unused labels.
//
@@ -371,10 +418,10 @@
}
ASSERT((instr & 7*B25) == 5*B25); // b, bl, or blx imm24
int imm26 = ((instr & Imm24Mask) << 8) >> 6;
- if ((instr & CondMask) == nv && (instr & B24) != 0)
+ if ((instr & CondMask) == nv && (instr & B24) != 0) {
// blx uses bit 24 to encode bit 2 of imm26
imm26 += 2;
-
+ }
return pos + kPcLoadDelta + imm26;
}
@@ -902,6 +949,10 @@
if (dst.is(pc)) {
WriteRecordedPositions();
}
+ // Don't allow nop instructions in the form mov rn, rn to be generated using
+ // the mov instruction. They must be generated using nop(int)
+ // pseudo instructions.
+ ASSERT(!(src.is_reg() && src.rm().is(dst) && s == LeaveCC && cond == al));
addrmod1(cond | 13*B21 | s, r0, dst, src);
}
@@ -1691,6 +1742,13 @@
// Pseudo instructions.
+void Assembler::nop(int type) {
+ // This is mov rx, rx.
+ ASSERT(0 <= type && type <= 14); // mov pc, pc is not a nop.
+ emit(al | 13*B21 | type*B12 | type);
+}
+
+
void Assembler::lea(Register dst,
const MemOperand& x,
SBit s,
@@ -1726,11 +1784,6 @@
}
-void Assembler::BlockConstPoolFor(int instructions) {
- BlockConstPoolBefore(pc_offset() + instructions * kInstrSize);
-}
-
-
// Debugging.
void Assembler::RecordJSReturn() {
WriteRecordedPositions();
@@ -1894,12 +1947,17 @@
// However, some small sequences of instructions must not be broken up by the
// insertion of a constant pool; such sequences are protected by setting
- // no_const_pool_before_, which is checked here. Also, recursive calls to
- // CheckConstPool are blocked by no_const_pool_before_.
- if (pc_offset() < no_const_pool_before_) {
+ // either const_pool_blocked_nesting_ or no_const_pool_before_, which are
+ // both checked here. Also, recursive calls to CheckConstPool are blocked by
+ // no_const_pool_before_.
+ if (const_pool_blocked_nesting_ > 0 || pc_offset() < no_const_pool_before_) {
// Emission is currently blocked; make sure we try again as soon as
// possible.
- next_buffer_check_ = no_const_pool_before_;
+ if (const_pool_blocked_nesting_ > 0) {
+ next_buffer_check_ = pc_offset() + kInstrSize;
+ } else {
+ next_buffer_check_ = no_const_pool_before_;
+ }
// Something is wrong if emission is forced and blocked at the same time.
ASSERT(!force_emit);
diff --git a/src/arm/assembler-arm.h b/src/arm/assembler-arm.h
index 98be7b5..e6284d3 100644
--- a/src/arm/assembler-arm.h
+++ b/src/arm/assembler-arm.h
@@ -896,7 +896,7 @@
const Condition cond = al);
// Pseudo instructions
- void nop() { mov(r0, Operand(r0)); }
+ void nop(int type = 0);
void push(Register src, Condition cond = al) {
str(src, MemOperand(sp, 4, NegPreIndex), cond);
@@ -925,9 +925,21 @@
// Check whether an immediate fits an addressing mode 1 instruction.
bool ImmediateFitsAddrMode1Instruction(int32_t imm32);
- // Postpone the generation of the constant pool for the specified number of
- // instructions.
- void BlockConstPoolFor(int instructions);
+ // Class for scoping postponing the constant pool generation.
+ class BlockConstPoolScope {
+ public:
+ explicit BlockConstPoolScope(Assembler* assem) : assem_(assem) {
+ assem_->StartBlockConstPool();
+ }
+ ~BlockConstPoolScope() {
+ assem_->EndBlockConstPool();
+ }
+
+ private:
+ Assembler* assem_;
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(BlockConstPoolScope);
+ };
// Debugging
@@ -946,14 +958,30 @@
int current_position() const { return current_position_; }
int current_statement_position() const { return current_position_; }
+ void StartBlockConstPool() {
+ const_pool_blocked_nesting_++;
+ }
+ void EndBlockConstPool() {
+ const_pool_blocked_nesting_--;
+ }
+
+ // Read/patch instructions
+ static Instr instr_at(byte* pc) { return *reinterpret_cast<Instr*>(pc); }
+ static void instr_at_put(byte* pc, Instr instr) {
+ *reinterpret_cast<Instr*>(pc) = instr;
+ }
+ static bool IsNop(Instr instr, int type = 0);
+ static bool IsBranch(Instr instr);
+ static int GetBranchOffset(Instr instr);
+ static bool IsLdrRegisterImmediate(Instr instr);
+ static int GetLdrRegisterImmediateOffset(Instr instr);
+ static Instr SetLdrRegisterImmediateOffset(Instr instr, int offset);
+
+
protected:
int buffer_space() const { return reloc_info_writer.pos() - pc_; }
// Read/patch instructions
- static Instr instr_at(byte* pc) { return *reinterpret_cast<Instr*>(pc); }
- void instr_at_put(byte* pc, Instr instr) {
- *reinterpret_cast<Instr*>(pc) = instr;
- }
Instr instr_at(int pos) { return *reinterpret_cast<Instr*>(buffer_ + pos); }
void instr_at_put(int pos, Instr instr) {
*reinterpret_cast<Instr*>(buffer_ + pos) = instr;
@@ -1022,8 +1050,9 @@
// distance between pools.
static const int kMaxDistBetweenPools = 4*KB - 2*kBufferCheckInterval;
- // Emission of the constant pool may be blocked in some code sequences
- int no_const_pool_before_; // block emission before this pc offset
+ // Emission of the constant pool may be blocked in some code sequences.
+ int const_pool_blocked_nesting_; // Block emission if this is not zero.
+ int no_const_pool_before_; // Block emission before this pc offset.
// Keep track of the last emitted pool to guarantee a maximal distance
int last_const_pool_end_; // pc offset following the last constant pool
@@ -1075,6 +1104,7 @@
friend class RegExpMacroAssemblerARM;
friend class RelocInfo;
friend class CodePatcher;
+ friend class BlockConstPoolScope;
};
} } // namespace v8::internal
diff --git a/src/arm/builtins-arm.cc b/src/arm/builtins-arm.cc
index 91e896d..7bb8c46 100644
--- a/src/arm/builtins-arm.cc
+++ b/src/arm/builtins-arm.cc
@@ -593,7 +593,7 @@
__ bind(&loop);
__ str(r7, MemOperand(r5, kPointerSize, PostIndex));
__ bind(&entry);
- __ cmp(r5, Operand(r6));
+ __ cmp(r5, r6);
__ b(lt, &loop);
}
@@ -666,7 +666,7 @@
__ bind(&loop);
__ str(r7, MemOperand(r2, kPointerSize, PostIndex));
__ bind(&entry);
- __ cmp(r2, Operand(r6));
+ __ cmp(r2, r6);
__ b(lt, &loop);
}
@@ -863,7 +863,7 @@
__ ldr(r0, MemOperand(r0)); // dereference handle
__ push(r0); // push parameter
__ bind(&entry);
- __ cmp(r4, Operand(r2));
+ __ cmp(r4, r2);
__ b(ne, &loop);
// Initialize all JavaScript callee-saved registers, since they will be seen
@@ -1213,7 +1213,7 @@
Label invoke, dont_adapt_arguments;
Label enough, too_few;
- __ cmp(r0, Operand(r2));
+ __ cmp(r0, r2);
__ b(lt, &too_few);
__ cmp(r2, Operand(SharedFunctionInfo::kDontAdaptArgumentsSentinel));
__ b(eq, &dont_adapt_arguments);
diff --git a/src/arm/codegen-arm.cc b/src/arm/codegen-arm.cc
index b2fe05f..3594aab 100644
--- a/src/arm/codegen-arm.cc
+++ b/src/arm/codegen-arm.cc
@@ -206,7 +206,7 @@
#ifdef DEBUG
JumpTarget verified_true;
- __ cmp(r0, Operand(cp));
+ __ cmp(r0, cp);
verified_true.Branch(eq);
__ stop("NewContext: r0 is expected to be the same as cp");
verified_true.Bind();
@@ -247,29 +247,10 @@
}
// Store the arguments object. This must happen after context
- // initialization because the arguments object may be stored in the
- // context.
- if (scope()->arguments() != NULL) {
- Comment cmnt(masm_, "[ allocate arguments object");
- ASSERT(scope()->arguments_shadow() != NULL);
- Variable* arguments = scope()->arguments()->var();
- Variable* shadow = scope()->arguments_shadow()->var();
- ASSERT(arguments != NULL && arguments->slot() != NULL);
- ASSERT(shadow != NULL && shadow->slot() != NULL);
- ArgumentsAccessStub stub(ArgumentsAccessStub::NEW_OBJECT);
- __ ldr(r2, frame_->Function());
- // The receiver is below the arguments, the return address, and the
- // frame pointer on the stack.
- const int kReceiverDisplacement = 2 + scope()->num_parameters();
- __ add(r1, fp, Operand(kReceiverDisplacement * kPointerSize));
- __ mov(r0, Operand(Smi::FromInt(scope()->num_parameters())));
- frame_->Adjust(3);
- __ stm(db_w, sp, r0.bit() | r1.bit() | r2.bit());
- frame_->CallStub(&stub, 3);
- frame_->EmitPush(r0);
- StoreToSlot(arguments->slot(), NOT_CONST_INIT);
- StoreToSlot(shadow->slot(), NOT_CONST_INIT);
- frame_->Drop(); // Value is no longer needed.
+ // initialization because the arguments object may be stored in
+ // the context.
+ if (ArgumentsMode() != NO_ARGUMENTS_ALLOCATION) {
+ StoreArgumentsObject(true);
}
// Initialize ThisFunction reference if present.
@@ -353,37 +334,34 @@
frame_->CallRuntime(Runtime::kTraceExit, 1);
}
+#ifdef DEBUG
// Add a label for checking the size of the code used for returning.
Label check_exit_codesize;
masm_->bind(&check_exit_codesize);
+#endif
+ // Make sure that the constant pool is not emitted inside of the return
+ // sequence.
+ { Assembler::BlockConstPoolScope block_const_pool(masm_);
+ // Tear down the frame which will restore the caller's frame pointer and
+ // the link register.
+ frame_->Exit();
- // Calculate the exact length of the return sequence and make sure that
- // the constant pool is not emitted inside of the return sequence.
- int32_t sp_delta = (scope()->num_parameters() + 1) * kPointerSize;
- int return_sequence_length = Assembler::kJSReturnSequenceLength;
- if (!masm_->ImmediateFitsAddrMode1Instruction(sp_delta)) {
- // Additional mov instruction generated.
- return_sequence_length++;
+ // Here we use masm_-> instead of the __ macro to avoid the code coverage
+ // tool from instrumenting as we rely on the code size here.
+ int32_t sp_delta = (scope()->num_parameters() + 1) * kPointerSize;
+ masm_->add(sp, sp, Operand(sp_delta));
+ masm_->Jump(lr);
}
- masm_->BlockConstPoolFor(return_sequence_length);
- // Tear down the frame which will restore the caller's frame pointer and
- // the link register.
- frame_->Exit();
-
- // Here we use masm_-> instead of the __ macro to avoid the code coverage
- // tool from instrumenting as we rely on the code size here.
- masm_->add(sp, sp, Operand(sp_delta));
- masm_->Jump(lr);
-
+#ifdef DEBUG
// Check that the size of the code used for returning matches what is
- // expected by the debugger. The add instruction above is an addressing
- // mode 1 instruction where there are restrictions on which immediate values
- // can be encoded in the instruction and which immediate values requires
- // use of an additional instruction for moving the immediate to a temporary
- // register.
- ASSERT_EQ(return_sequence_length,
- masm_->InstructionsGeneratedSince(&check_exit_codesize));
+ // expected by the debugger. If the sp_delts above cannot be encoded in the
+ // add instruction the add will generate two instructions.
+ int return_sequence_length =
+ masm_->InstructionsGeneratedSince(&check_exit_codesize);
+ CHECK(return_sequence_length == Assembler::kJSReturnSequenceLength ||
+ return_sequence_length == Assembler::kJSReturnSequenceLength + 1);
+#endif
}
// Adjust for function-level loop nesting.
@@ -393,6 +371,7 @@
// Code generation state must be reset.
ASSERT(!has_cc());
ASSERT(state_ == NULL);
+ ASSERT(loop_nesting() == 0);
ASSERT(!function_return_is_shadowed_);
function_return_.Unuse();
DeleteFrame();
@@ -606,6 +585,66 @@
}
+ArgumentsAllocationMode CodeGenerator::ArgumentsMode() {
+ if (scope()->arguments() == NULL) return NO_ARGUMENTS_ALLOCATION;
+ ASSERT(scope()->arguments_shadow() != NULL);
+ // We don't want to do lazy arguments allocation for functions that
+ // have heap-allocated contexts, because it interfers with the
+ // uninitialized const tracking in the context objects.
+ return (scope()->num_heap_slots() > 0)
+ ? EAGER_ARGUMENTS_ALLOCATION
+ : LAZY_ARGUMENTS_ALLOCATION;
+}
+
+
+void CodeGenerator::StoreArgumentsObject(bool initial) {
+ VirtualFrame::SpilledScope spilled_scope(frame_);
+
+ ArgumentsAllocationMode mode = ArgumentsMode();
+ ASSERT(mode != NO_ARGUMENTS_ALLOCATION);
+
+ Comment cmnt(masm_, "[ store arguments object");
+ if (mode == LAZY_ARGUMENTS_ALLOCATION && initial) {
+ // When using lazy arguments allocation, we store the hole value
+ // as a sentinel indicating that the arguments object hasn't been
+ // allocated yet.
+ __ LoadRoot(ip, Heap::kTheHoleValueRootIndex);
+ frame_->EmitPush(ip);
+ } else {
+ ArgumentsAccessStub stub(ArgumentsAccessStub::NEW_OBJECT);
+ __ ldr(r2, frame_->Function());
+ // The receiver is below the arguments, the return address, and the
+ // frame pointer on the stack.
+ const int kReceiverDisplacement = 2 + scope()->num_parameters();
+ __ add(r1, fp, Operand(kReceiverDisplacement * kPointerSize));
+ __ mov(r0, Operand(Smi::FromInt(scope()->num_parameters())));
+ frame_->Adjust(3);
+ __ stm(db_w, sp, r0.bit() | r1.bit() | r2.bit());
+ frame_->CallStub(&stub, 3);
+ frame_->EmitPush(r0);
+ }
+
+ Variable* arguments = scope()->arguments()->var();
+ Variable* shadow = scope()->arguments_shadow()->var();
+ ASSERT(arguments != NULL && arguments->slot() != NULL);
+ ASSERT(shadow != NULL && shadow->slot() != NULL);
+ JumpTarget done;
+ if (mode == LAZY_ARGUMENTS_ALLOCATION && !initial) {
+ // We have to skip storing into the arguments slot if it has
+ // already been written to. This can happen if the a function
+ // has a local variable named 'arguments'.
+ LoadFromSlot(scope()->arguments()->var()->slot(), NOT_INSIDE_TYPEOF);
+ frame_->EmitPop(r0);
+ __ LoadRoot(ip, Heap::kTheHoleValueRootIndex);
+ __ cmp(r0, ip);
+ done.Branch(ne);
+ }
+ StoreToSlot(arguments->slot(), NOT_CONST_INIT);
+ if (mode == LAZY_ARGUMENTS_ALLOCATION) done.Bind();
+ StoreToSlot(shadow->slot(), NOT_CONST_INIT);
+}
+
+
void CodeGenerator::LoadTypeofExpression(Expression* expr) {
// Special handling of identifiers as subexpressions of typeof.
VirtualFrame::SpilledScope spilled_scope(frame_);
@@ -622,7 +661,7 @@
} else if (variable != NULL && variable->slot() != NULL) {
// For a variable that rewrites to a slot, we signal it is the immediate
// subexpression of a typeof.
- LoadFromSlot(variable->slot(), INSIDE_TYPEOF);
+ LoadFromSlotCheckForArguments(variable->slot(), INSIDE_TYPEOF);
frame_->SpillAll();
} else {
// Anything else can be handled normally.
@@ -1466,6 +1505,188 @@
}
+void CodeGenerator::CallApplyLazy(Expression* applicand,
+ Expression* receiver,
+ VariableProxy* arguments,
+ int position) {
+ // An optimized implementation of expressions of the form
+ // x.apply(y, arguments).
+ // If the arguments object of the scope has not been allocated,
+ // and x.apply is Function.prototype.apply, this optimization
+ // just copies y and the arguments of the current function on the
+ // stack, as receiver and arguments, and calls x.
+ // In the implementation comments, we call x the applicand
+ // and y the receiver.
+ VirtualFrame::SpilledScope spilled_scope(frame_);
+
+ ASSERT(ArgumentsMode() == LAZY_ARGUMENTS_ALLOCATION);
+ ASSERT(arguments->IsArguments());
+
+ // Load applicand.apply onto the stack. This will usually
+ // give us a megamorphic load site. Not super, but it works.
+ LoadAndSpill(applicand);
+ Handle<String> name = Factory::LookupAsciiSymbol("apply");
+ __ mov(r2, Operand(name));
+ frame_->CallLoadIC(RelocInfo::CODE_TARGET);
+ frame_->EmitPush(r0);
+
+ // Load the receiver and the existing arguments object onto the
+ // expression stack. Avoid allocating the arguments object here.
+ LoadAndSpill(receiver);
+ LoadFromSlot(scope()->arguments()->var()->slot(), NOT_INSIDE_TYPEOF);
+
+ // Emit the source position information after having loaded the
+ // receiver and the arguments.
+ CodeForSourcePosition(position);
+ // Contents of the stack at this point:
+ // sp[0]: arguments object of the current function or the hole.
+ // sp[1]: receiver
+ // sp[2]: applicand.apply
+ // sp[3]: applicand.
+
+ // Check if the arguments object has been lazily allocated
+ // already. If so, just use that instead of copying the arguments
+ // from the stack. This also deals with cases where a local variable
+ // named 'arguments' has been introduced.
+ __ ldr(r0, MemOperand(sp, 0));
+
+ Label slow, done;
+ __ LoadRoot(ip, Heap::kTheHoleValueRootIndex);
+ __ cmp(ip, r0);
+ __ b(ne, &slow);
+
+ Label build_args;
+ // Get rid of the arguments object probe.
+ frame_->Drop();
+ // Stack now has 3 elements on it.
+ // Contents of stack at this point:
+ // sp[0]: receiver
+ // sp[1]: applicand.apply
+ // sp[2]: applicand.
+
+ // Check that the receiver really is a JavaScript object.
+ __ ldr(r0, MemOperand(sp, 0));
+ __ BranchOnSmi(r0, &build_args);
+ // We allow all JSObjects including JSFunctions. As long as
+ // JS_FUNCTION_TYPE is the last instance type and it is right
+ // after LAST_JS_OBJECT_TYPE, we do not have to check the upper
+ // bound.
+ ASSERT(LAST_TYPE == JS_FUNCTION_TYPE);
+ ASSERT(JS_FUNCTION_TYPE == LAST_JS_OBJECT_TYPE + 1);
+ __ CompareObjectType(r0, r1, r2, FIRST_JS_OBJECT_TYPE);
+ __ b(lt, &build_args);
+
+ // Check that applicand.apply is Function.prototype.apply.
+ __ ldr(r0, MemOperand(sp, kPointerSize));
+ __ BranchOnSmi(r0, &build_args);
+ __ CompareObjectType(r0, r1, r2, JS_FUNCTION_TYPE);
+ __ b(ne, &build_args);
+ __ ldr(r0, FieldMemOperand(r0, JSFunction::kSharedFunctionInfoOffset));
+ Handle<Code> apply_code(Builtins::builtin(Builtins::FunctionApply));
+ __ ldr(r1, FieldMemOperand(r0, SharedFunctionInfo::kCodeOffset));
+ __ cmp(r1, Operand(apply_code));
+ __ b(ne, &build_args);
+
+ // Check that applicand is a function.
+ __ ldr(r1, MemOperand(sp, 2 * kPointerSize));
+ __ BranchOnSmi(r1, &build_args);
+ __ CompareObjectType(r1, r2, r3, JS_FUNCTION_TYPE);
+ __ b(ne, &build_args);
+
+ // Copy the arguments to this function possibly from the
+ // adaptor frame below it.
+ Label invoke, adapted;
+ __ ldr(r2, MemOperand(fp, StandardFrameConstants::kCallerFPOffset));
+ __ ldr(r3, MemOperand(r2, StandardFrameConstants::kContextOffset));
+ __ cmp(r3, Operand(Smi::FromInt(StackFrame::ARGUMENTS_ADAPTOR)));
+ __ b(eq, &adapted);
+
+ // No arguments adaptor frame. Copy fixed number of arguments.
+ __ mov(r0, Operand(scope()->num_parameters()));
+ for (int i = 0; i < scope()->num_parameters(); i++) {
+ __ ldr(r2, frame_->ParameterAt(i));
+ __ push(r2);
+ }
+ __ jmp(&invoke);
+
+ // Arguments adaptor frame present. Copy arguments from there, but
+ // avoid copying too many arguments to avoid stack overflows.
+ __ bind(&adapted);
+ static const uint32_t kArgumentsLimit = 1 * KB;
+ __ ldr(r0, MemOperand(r2, ArgumentsAdaptorFrameConstants::kLengthOffset));
+ __ mov(r0, Operand(r0, LSR, kSmiTagSize));
+ __ mov(r3, r0);
+ __ cmp(r0, Operand(kArgumentsLimit));
+ __ b(gt, &build_args);
+
+ // Loop through the arguments pushing them onto the execution
+ // stack. We don't inform the virtual frame of the push, so we don't
+ // have to worry about getting rid of the elements from the virtual
+ // frame.
+ Label loop;
+ // r3 is a small non-negative integer, due to the test above.
+ __ cmp(r3, Operand(0));
+ __ b(eq, &invoke);
+ // Compute the address of the first argument.
+ __ add(r2, r2, Operand(r3, LSL, kPointerSizeLog2));
+ __ add(r2, r2, Operand(kPointerSize));
+ __ bind(&loop);
+ // Post-decrement argument address by kPointerSize on each iteration.
+ __ ldr(r4, MemOperand(r2, kPointerSize, NegPostIndex));
+ __ push(r4);
+ __ sub(r3, r3, Operand(1), SetCC);
+ __ b(gt, &loop);
+
+ // Invoke the function.
+ __ bind(&invoke);
+ ParameterCount actual(r0);
+ __ InvokeFunction(r1, actual, CALL_FUNCTION);
+ // Drop applicand.apply and applicand from the stack, and push
+ // the result of the function call, but leave the spilled frame
+ // unchanged, with 3 elements, so it is correct when we compile the
+ // slow-case code.
+ __ add(sp, sp, Operand(2 * kPointerSize));
+ __ push(r0);
+ // Stack now has 1 element:
+ // sp[0]: result
+ __ jmp(&done);
+
+ // Slow-case: Allocate the arguments object since we know it isn't
+ // there, and fall-through to the slow-case where we call
+ // applicand.apply.
+ __ bind(&build_args);
+ // Stack now has 3 elements, because we have jumped from where:
+ // sp[0]: receiver
+ // sp[1]: applicand.apply
+ // sp[2]: applicand.
+ StoreArgumentsObject(false);
+
+ // Stack and frame now have 4 elements.
+ __ bind(&slow);
+
+ // Generic computation of x.apply(y, args) with no special optimization.
+ // Flip applicand.apply and applicand on the stack, so
+ // applicand looks like the receiver of the applicand.apply call.
+ // Then process it as a normal function call.
+ __ ldr(r0, MemOperand(sp, 3 * kPointerSize));
+ __ ldr(r1, MemOperand(sp, 2 * kPointerSize));
+ __ str(r0, MemOperand(sp, 2 * kPointerSize));
+ __ str(r1, MemOperand(sp, 3 * kPointerSize));
+
+ CallFunctionStub call_function(2, NOT_IN_LOOP, NO_CALL_FUNCTION_FLAGS);
+ frame_->CallStub(&call_function, 3);
+ // The function and its two arguments have been dropped.
+ frame_->Drop(); // Drop the receiver as well.
+ frame_->EmitPush(r0);
+ // Stack now has 1 element:
+ // sp[0]: result
+ __ bind(&done);
+
+ // Restore the context register after a call.
+ __ ldr(cp, frame_->Context());
+}
+
+
void CodeGenerator::Branch(bool if_true, JumpTarget* target) {
VirtualFrame::SpilledScope spilled_scope(frame_);
ASSERT(has_cc());
@@ -1771,7 +1992,7 @@
}
#ifdef DEBUG
JumpTarget verified_true;
- __ cmp(r0, Operand(cp));
+ __ cmp(r0, cp);
verified_true.Branch(eq);
__ stop("PushContext: r0 is expected to be the same as cp");
verified_true.Bind();
@@ -2248,7 +2469,7 @@
__ ldr(r0, frame_->ElementAt(0)); // load the current count
__ ldr(r1, frame_->ElementAt(1)); // load the length
- __ cmp(r0, Operand(r1)); // compare to the array length
+ __ cmp(r0, r1); // compare to the array length
node->break_target()->Branch(hs);
__ ldr(r0, frame_->ElementAt(0));
@@ -2802,6 +3023,34 @@
}
+void CodeGenerator::LoadFromSlotCheckForArguments(Slot* slot,
+ TypeofState state) {
+ LoadFromSlot(slot, state);
+
+ // Bail out quickly if we're not using lazy arguments allocation.
+ if (ArgumentsMode() != LAZY_ARGUMENTS_ALLOCATION) return;
+
+ // ... or if the slot isn't a non-parameter arguments slot.
+ if (slot->type() == Slot::PARAMETER || !slot->is_arguments()) return;
+
+ VirtualFrame::SpilledScope spilled_scope(frame_);
+
+ // Load the loaded value from the stack into r0 but leave it on the
+ // stack.
+ __ ldr(r0, MemOperand(sp, 0));
+
+ // If the loaded value is the sentinel that indicates that we
+ // haven't loaded the arguments object yet, we need to do it now.
+ JumpTarget exit;
+ __ LoadRoot(ip, Heap::kTheHoleValueRootIndex);
+ __ cmp(r0, ip);
+ exit.Branch(ne);
+ frame_->Drop();
+ StoreArgumentsObject(false);
+ exit.Bind();
+}
+
+
void CodeGenerator::StoreToSlot(Slot* slot, InitState init_state) {
ASSERT(slot != NULL);
if (slot->type() == Slot::LOOKUP) {
@@ -2940,20 +3189,13 @@
__ bind(&fast);
}
- // All extension objects were empty and it is safe to use a global
- // load IC call.
- Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
// Load the global object.
LoadGlobal();
- // Setup the name register.
+ // Setup the name register and call load IC.
__ mov(r2, Operand(slot->var()->name()));
- // Call IC stub.
- if (typeof_state == INSIDE_TYPEOF) {
- frame_->CallCodeObject(ic, RelocInfo::CODE_TARGET, 0);
- } else {
- frame_->CallCodeObject(ic, RelocInfo::CODE_TARGET_CONTEXT, 0);
- }
-
+ frame_->CallLoadIC(typeof_state == INSIDE_TYPEOF
+ ? RelocInfo::CODE_TARGET
+ : RelocInfo::CODE_TARGET_CONTEXT);
// Drop the global object. The result is in r0.
frame_->Drop();
}
@@ -2964,7 +3206,7 @@
int original_height = frame_->height();
#endif
Comment cmnt(masm_, "[ Slot");
- LoadFromSlot(node, NOT_INSIDE_TYPEOF);
+ LoadFromSlotCheckForArguments(node, NOT_INSIDE_TYPEOF);
ASSERT(frame_->height() == original_height + 1);
}
@@ -3422,21 +3664,37 @@
// JavaScript example: 'object.foo(1, 2, 3)' or 'map["key"](1, 2, 3)'
// ------------------------------------------------------------------
- LoadAndSpill(property->obj()); // Receiver.
- // Load the arguments.
- int arg_count = args->length();
- for (int i = 0; i < arg_count; i++) {
- LoadAndSpill(args->at(i));
- }
+ Handle<String> name = Handle<String>::cast(literal->handle());
- // Set the name register and call the IC initialization code.
- __ mov(r2, Operand(literal->handle()));
- InLoopFlag in_loop = loop_nesting() > 0 ? IN_LOOP : NOT_IN_LOOP;
- Handle<Code> stub = ComputeCallInitialize(arg_count, in_loop);
- CodeForSourcePosition(node->position());
- frame_->CallCodeObject(stub, RelocInfo::CODE_TARGET, arg_count + 1);
- __ ldr(cp, frame_->Context());
- frame_->EmitPush(r0);
+ if (ArgumentsMode() == LAZY_ARGUMENTS_ALLOCATION &&
+ name->IsEqualTo(CStrVector("apply")) &&
+ args->length() == 2 &&
+ args->at(1)->AsVariableProxy() != NULL &&
+ args->at(1)->AsVariableProxy()->IsArguments()) {
+ // Use the optimized Function.prototype.apply that avoids
+ // allocating lazily allocated arguments objects.
+ CallApplyLazy(property->obj(),
+ args->at(0),
+ args->at(1)->AsVariableProxy(),
+ node->position());
+
+ } else {
+ LoadAndSpill(property->obj()); // Receiver.
+ // Load the arguments.
+ int arg_count = args->length();
+ for (int i = 0; i < arg_count; i++) {
+ LoadAndSpill(args->at(i));
+ }
+
+ // Set the name register and call the IC initialization code.
+ __ mov(r2, Operand(name));
+ InLoopFlag in_loop = loop_nesting() > 0 ? IN_LOOP : NOT_IN_LOOP;
+ Handle<Code> stub = ComputeCallInitialize(arg_count, in_loop);
+ CodeForSourcePosition(node->position());
+ frame_->CallCodeObject(stub, RelocInfo::CODE_TARGET, arg_count + 1);
+ __ ldr(cp, frame_->Context());
+ frame_->EmitPush(r0);
+ }
} else {
// -------------------------------------------
@@ -3974,19 +4232,49 @@
Label slow_allocate_heapnumber;
Label heapnumber_allocated;
- __ AllocateHeapNumber(r0, r1, r2, &slow_allocate_heapnumber);
+ __ AllocateHeapNumber(r4, r1, r2, &slow_allocate_heapnumber);
__ jmp(&heapnumber_allocated);
__ bind(&slow_allocate_heapnumber);
+ // To allocate a heap number, and ensure that it is not a smi, we
+ // call the runtime function FUnaryMinus on 0, returning the double
+ // -0.0. A new, distinct heap number is returned each time.
__ mov(r0, Operand(Smi::FromInt(0)));
__ push(r0);
__ CallRuntime(Runtime::kNumberUnaryMinus, 1);
+ __ mov(r4, Operand(r0));
__ bind(&heapnumber_allocated);
- __ PrepareCallCFunction(1, r1);
- __ CallCFunction(
- ExternalReference::fill_heap_number_with_random_function(), 1);
- frame_->EmitPush(r0);
+
+ // Convert 32 random bits in r0 to 0.(32 random bits) in a double
+ // by computing:
+ // ( 1.(20 0s)(32 random bits) x 2^20 ) - (1.0 x 2^20)).
+ if (CpuFeatures::IsSupported(VFP3)) {
+ __ PrepareCallCFunction(0, r1);
+ __ CallCFunction(ExternalReference::random_uint32_function(), 0);
+
+ CpuFeatures::Scope scope(VFP3);
+ // 0x41300000 is the top half of 1.0 x 2^20 as a double.
+ // Create this constant using mov/orr to avoid PC relative load.
+ __ mov(r1, Operand(0x41000000));
+ __ orr(r1, r1, Operand(0x300000));
+ // Move 0x41300000xxxxxxxx (x = random bits) to VFP.
+ __ vmov(d7, r0, r1);
+ // Move 0x4130000000000000 to VFP.
+ __ mov(r0, Operand(0));
+ __ vmov(d8, r0, r1);
+ // Subtract and store the result in the heap number.
+ __ vsub(d7, d7, d8);
+ __ sub(r0, r4, Operand(kHeapObjectTag));
+ __ vstr(d7, r0, HeapNumber::kValueOffset);
+ frame_->EmitPush(r4);
+ } else {
+ __ mov(r0, Operand(r4));
+ __ PrepareCallCFunction(1, r1);
+ __ CallCFunction(
+ ExternalReference::fill_heap_number_with_random_function(), 1);
+ frame_->EmitPush(r0);
+ }
}
@@ -4172,18 +4460,20 @@
frame_->EmitPush(r0);
return;
}
- Handle<FixedArray> cache_obj(
- FixedArray::cast(jsfunction_result_caches->get(cache_id)));
Load(args->at(1));
frame_->EmitPop(r2);
+ __ ldr(r1, ContextOperand(cp, Context::GLOBAL_INDEX));
+ __ ldr(r1, FieldMemOperand(r1, GlobalObject::kGlobalContextOffset));
+ __ ldr(r1, ContextOperand(r1, Context::JSFUNCTION_RESULT_CACHES_INDEX));
+ __ ldr(r1, FieldMemOperand(r1, FixedArray::OffsetOfElementAt(cache_id)));
+
DeferredSearchCache* deferred = new DeferredSearchCache(r0, r1, r2);
const int kFingerOffset =
FixedArray::OffsetOfElementAt(JSFunctionResultCache::kFingerIndex);
ASSERT(kSmiTag == 0 && kSmiTagSize == 1);
- __ mov(r1, Operand(cache_obj));
__ ldr(r0, FieldMemOperand(r1, kFingerOffset));
// r0 now holds finger offset as a smi.
__ add(r3, r1, Operand(FixedArray::kHeaderSize - kHeapObjectTag));
@@ -4255,7 +4545,7 @@
LoadAndSpill(args->at(1));
frame_->EmitPop(r0);
frame_->EmitPop(r1);
- __ cmp(r0, Operand(r1));
+ __ cmp(r0, r1);
cc_reg_ = eq;
}
@@ -4935,6 +5225,97 @@
}
+class DeferredReferenceGetNamedValue: public DeferredCode {
+ public:
+ explicit DeferredReferenceGetNamedValue(Handle<String> name) : name_(name) {
+ set_comment("[ DeferredReferenceGetNamedValue");
+ }
+
+ virtual void BeforeGenerate();
+ virtual void Generate();
+ virtual void AfterGenerate();
+
+ private:
+ Handle<String> name_;
+};
+
+
+void DeferredReferenceGetNamedValue::BeforeGenerate() {
+ __ StartBlockConstPool();
+}
+
+
+void DeferredReferenceGetNamedValue::Generate() {
+ __ IncrementCounter(&Counters::named_load_inline_miss, 1, r1, r2);
+ // Setup the name register and call load IC.
+ __ mov(r2, Operand(name_));
+ Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
+ __ Call(ic, RelocInfo::CODE_TARGET);
+ // The call must be followed by a nop(1) instruction to indicate that the
+ // inobject has been inlined.
+ __ nop(NAMED_PROPERTY_LOAD_INLINED);
+}
+
+
+void DeferredReferenceGetNamedValue::AfterGenerate() {
+ __ EndBlockConstPool();
+}
+
+
+void CodeGenerator::EmitNamedLoad(Handle<String> name, bool is_contextual) {
+ if (is_contextual || scope()->is_global_scope() || loop_nesting() == 0) {
+ Comment cmnt(masm(), "[ Load from named Property");
+ // Setup the name register and call load IC.
+ __ mov(r2, Operand(name));
+ frame_->CallLoadIC(is_contextual
+ ? RelocInfo::CODE_TARGET_CONTEXT
+ : RelocInfo::CODE_TARGET);
+ } else {
+ // Inline the inobject property case.
+ Comment cmnt(masm(), "[ Inlined named property load");
+
+ DeferredReferenceGetNamedValue* deferred =
+ new DeferredReferenceGetNamedValue(name);
+
+ // The following instructions are the inlined load of an in-object property.
+ // Parts of this code is patched, so the exact instructions generated needs
+ // to be fixed. Therefore the instruction pool is blocked when generating
+ // this code
+#ifdef DEBUG
+ int kInlinedNamedLoadInstructions = 8;
+ Label check_inlined_codesize;
+ masm_->bind(&check_inlined_codesize);
+#endif
+ { Assembler::BlockConstPoolScope block_const_pool(masm_);
+ // Load the receiver from the stack.
+ __ ldr(r1, MemOperand(sp, 0));
+
+ // Check that the receiver is a heap object.
+ __ tst(r1, Operand(kSmiTagMask));
+ deferred->Branch(eq);
+
+ // Check the map. The null map used below is patched by the inline cache
+ // code.
+ __ ldr(r2, FieldMemOperand(r1, HeapObject::kMapOffset));
+ __ mov(r3, Operand(Factory::null_value()));
+ __ cmp(r2, r3);
+ deferred->Branch(ne);
+
+ // Use initially use an invalid index. The index will be patched by the
+ // inline cache code.
+ __ ldr(r0, MemOperand(r1, 0));
+ }
+
+ // Make sure that the expected number of instructions are generated.
+ ASSERT_EQ(kInlinedNamedLoadInstructions,
+ masm_->InstructionsGeneratedSince(&check_inlined_codesize));
+
+ __ IncrementCounter(&Counters::named_load_inline, 1, r1, r2);
+ deferred->BindExit();
+ }
+}
+
+
void CodeGenerator::EmitKeyedLoad(bool is_global) {
Comment cmnt(masm_, "[ Load from keyed Property");
Handle<Code> ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize));
@@ -4986,24 +5367,16 @@
Comment cmnt(masm, "[ Load from Slot");
Slot* slot = expression_->AsVariableProxy()->AsVariable()->slot();
ASSERT(slot != NULL);
- cgen_->LoadFromSlot(slot, NOT_INSIDE_TYPEOF);
+ cgen_->LoadFromSlotCheckForArguments(slot, NOT_INSIDE_TYPEOF);
break;
}
case NAMED: {
- VirtualFrame* frame = cgen_->frame();
- Comment cmnt(masm, "[ Load from named Property");
- Handle<String> name(GetName());
Variable* var = expression_->AsVariableProxy()->AsVariable();
- Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
- // Setup the name register.
- __ mov(r2, Operand(name));
- ASSERT(var == NULL || var->is_global());
- RelocInfo::Mode rmode = (var == NULL)
- ? RelocInfo::CODE_TARGET
- : RelocInfo::CODE_TARGET_CONTEXT;
- frame->CallCodeObject(ic, rmode, 0);
- frame->EmitPush(r0);
+ bool is_global = var != NULL;
+ ASSERT(!is_global || var->is_global());
+ cgen_->EmitNamedLoad(GetName(), is_global);
+ cgen_->frame()->EmitPush(r0);
break;
}
@@ -5400,7 +5773,7 @@
Label not_identical;
Label heap_number, return_equal;
Register exp_mask_reg = r5;
- __ cmp(r0, Operand(r1));
+ __ cmp(r0, r1);
__ b(ne, ¬_identical);
// The two objects are identical. If we know that one of them isn't NaN then
@@ -5429,7 +5802,7 @@
__ cmp(r4, Operand(ODDBALL_TYPE));
__ b(ne, &return_equal);
__ LoadRoot(r2, Heap::kUndefinedValueRootIndex);
- __ cmp(r0, Operand(r2));
+ __ cmp(r0, r2);
__ b(ne, &return_equal);
if (cc == le) {
// undefined <= undefined should fail.
@@ -5992,8 +6365,7 @@
Register lhs,
Register rhs,
const Builtins::JavaScript& builtin) {
- Label slow, slow_pop_2_first, do_the_call;
- Label r0_is_smi, r1_is_smi, finished_loading_r0, finished_loading_r1;
+ Label slow, slow_reverse, do_the_call;
bool use_fp_registers = CpuFeatures::IsSupported(VFP3) && Token::MOD != op_;
ASSERT((lhs.is(r0) && rhs.is(r1)) || (lhs.is(r1) && rhs.is(r0)));
@@ -6002,7 +6374,7 @@
// Smi-smi case (overflow).
// Since both are Smis there is no heap number to overwrite, so allocate.
// The new heap number is in r5. r6 and r7 are scratch.
- __ AllocateHeapNumber(r5, r6, r7, &slow);
+ __ AllocateHeapNumber(r5, r6, r7, lhs.is(r0) ? &slow_reverse : &slow);
// If we have floating point hardware, inline ADD, SUB, MUL, and DIV,
// using registers d7 and d6 for the double values.
@@ -6032,11 +6404,15 @@
// We branch here if at least one of r0 and r1 is not a Smi.
__ bind(not_smi);
+ // After this point we have the left hand side in r1 and the right hand side
+ // in r0.
if (lhs.is(r0)) {
__ Swap(r0, r1, ip);
}
if (ShouldGenerateFPCode()) {
+ Label r0_is_smi, r1_is_smi, finished_loading_r0, finished_loading_r1;
+
if (runtime_operands_type_ == BinaryOpIC::DEFAULT) {
switch (op_) {
case Token::ADD:
@@ -6054,7 +6430,7 @@
if (mode_ == NO_OVERWRITE) {
// In the case where there is no chance of an overwritable float we may as
// well do the allocation immediately while r0 and r1 are untouched.
- __ AllocateHeapNumber(r5, r6, r7, &slow);
+ __ AllocateHeapNumber(r5, r6, r7, &slow);
}
// Move r0 to a double in r2-r3.
@@ -6097,11 +6473,22 @@
__ pop(lr);
}
+ // HEAP_NUMBERS stub is slower than GENERIC on a pair of smis.
+ // r0 is known to be a smi. If r1 is also a smi then switch to GENERIC.
+ Label r1_is_not_smi;
+ if (runtime_operands_type_ == BinaryOpIC::HEAP_NUMBERS) {
+ __ tst(r1, Operand(kSmiTagMask));
+ __ b(ne, &r1_is_not_smi);
+ GenerateTypeTransition(masm);
+ __ jmp(&r1_is_smi);
+ }
+
__ bind(&finished_loading_r0);
// Move r1 to a double in r0-r1.
__ tst(r1, Operand(kSmiTagMask));
__ b(eq, &r1_is_smi); // It's a Smi so don't check it's a heap number.
+ __ bind(&r1_is_not_smi);
__ CompareObjectType(r1, r4, r4, HEAP_NUMBER_TYPE);
__ b(ne, &slow);
if (mode_ == OVERWRITE_LEFT) {
@@ -6194,6 +6581,14 @@
__ pop(pc);
}
}
+
+
+ if (lhs.is(r0)) {
+ __ b(&slow);
+ __ bind(&slow_reverse);
+ __ Swap(r0, r1, ip);
+ }
+
// We jump to here if something goes wrong (one param is not a number of any
// sort or new-space allocation fails).
__ bind(&slow);
@@ -7743,7 +8138,8 @@
__ ldr(last_match_info_elements,
FieldMemOperand(r0, JSArray::kElementsOffset));
__ ldr(r0, FieldMemOperand(last_match_info_elements, HeapObject::kMapOffset));
- __ cmp(r0, Operand(Factory::fixed_array_map()));
+ __ LoadRoot(ip, kFixedArrayMapRootIndex);
+ __ cmp(r0, ip);
__ b(ne, &runtime);
// Check that the last match info has space for the capture registers and the
// additional information.
diff --git a/src/arm/codegen-arm.h b/src/arm/codegen-arm.h
index 5abd814..525b22b 100644
--- a/src/arm/codegen-arm.h
+++ b/src/arm/codegen-arm.h
@@ -144,6 +144,24 @@
// -------------------------------------------------------------------------
+// Arguments allocation mode
+
+enum ArgumentsAllocationMode {
+ NO_ARGUMENTS_ALLOCATION,
+ EAGER_ARGUMENTS_ALLOCATION,
+ LAZY_ARGUMENTS_ALLOCATION
+};
+
+
+// Different nop operations are used by the code generator to detect certain
+// states of the generated code.
+enum NopMarkerTypes {
+ NON_MARKING_NOP = 0,
+ NAMED_PROPERTY_LOAD_INLINED
+};
+
+
+// -------------------------------------------------------------------------
// CodeGenerator
class CodeGenerator: public AstVisitor {
@@ -241,6 +259,12 @@
// Main code generation function
void Generate(CompilationInfo* info);
+ // Returns the arguments allocation mode.
+ ArgumentsAllocationMode ArgumentsMode();
+
+ // Store the arguments object and allocate it if necessary.
+ void StoreArgumentsObject(bool initial);
+
// The following are used by class Reference.
void LoadReference(Reference* ref);
void UnloadReference(Reference* ref);
@@ -284,9 +308,14 @@
// Read a value from a slot and leave it on top of the expression stack.
void LoadFromSlot(Slot* slot, TypeofState typeof_state);
+ void LoadFromSlotCheckForArguments(Slot* slot, TypeofState state);
// Store the value on top of the stack to a slot.
void StoreToSlot(Slot* slot, InitState init_state);
+ // Load a named property, leaving it in r0. The receiver is passed on the
+ // stack, and remains there.
+ void EmitNamedLoad(Handle<String> name, bool is_contextual);
+
// Load a keyed property, leaving it in r0. The receiver and key are
// passed on the stack, and remain there.
void EmitKeyedLoad(bool is_global);
@@ -335,6 +364,14 @@
CallFunctionFlags flags,
int position);
+ // An optimized implementation of expressions of the form
+ // x.apply(y, arguments). We call x the applicand and y the receiver.
+ // The optimization avoids allocating an arguments object if possible.
+ void CallApplyLazy(Expression* applicand,
+ Expression* receiver,
+ VariableProxy* arguments,
+ int position);
+
// Control flow
void Branch(bool if_true, JumpTarget* target);
void CheckStack();
diff --git a/src/arm/cpu-arm.cc b/src/arm/cpu-arm.cc
index 55f31d4..d50c203 100644
--- a/src/arm/cpu-arm.cc
+++ b/src/arm/cpu-arm.cc
@@ -26,7 +26,7 @@
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// CPU specific code for arm independent of OS goes here.
-#if defined(__arm__)
+#ifdef __arm__
#include <sys/syscall.h> // for cache flushing.
#endif
@@ -35,6 +35,10 @@
#include "cpu.h"
#include "macro-assembler.h"
+#ifndef __arm__
+#include "simulator-arm.h" // for cache flushing.
+#endif
+
namespace v8 {
namespace internal {
@@ -46,9 +50,11 @@
void CPU::FlushICache(void* start, size_t size) {
#if !defined (__arm__)
// Not generating ARM instructions for C-code. This means that we are
- // building an ARM emulator based target. No I$ flushes are necessary.
+ // building an ARM emulator based target. We should notify the simulator
+ // that the Icache was flushed.
// None of this code ends up in the snapshot so there are no issues
// around whether or not to generate the code when building snapshots.
+ assembler::arm::Simulator::FlushICache(start, size);
#else
// Ideally, we would call
// syscall(__ARM_NR_cacheflush, start,
diff --git a/src/arm/disasm-arm.cc b/src/arm/disasm-arm.cc
index a89c6b8..4ba3094 100644
--- a/src/arm/disasm-arm.cc
+++ b/src/arm/disasm-arm.cc
@@ -34,10 +34,9 @@
// NameConverter converter;
// Disassembler d(converter);
// for (byte* pc = begin; pc < end;) {
-// char buffer[128];
-// buffer[0] = '\0';
+// v8::internal::EmbeddedVector<char, 256> buffer;
// byte* prev_pc = pc;
-// pc += d.InstructionDecode(buffer, sizeof buffer, pc);
+// pc += d.InstructionDecode(buffer, pc);
// printf("%p %08x %s\n",
// prev_pc, *reinterpret_cast<int32_t*>(prev_pc), buffer);
// }
diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc
index 62365ff..10ccf9c 100644
--- a/src/arm/full-codegen-arm.cc
+++ b/src/arm/full-codegen-arm.cc
@@ -194,36 +194,34 @@
__ CallRuntime(Runtime::kTraceExit, 1);
}
+#ifdef DEBUG
// Add a label for checking the size of the code used for returning.
Label check_exit_codesize;
masm_->bind(&check_exit_codesize);
-
- // Calculate the exact length of the return sequence and make sure that
- // the constant pool is not emitted inside of the return sequence.
- int num_parameters = scope()->num_parameters();
- int32_t sp_delta = (num_parameters + 1) * kPointerSize;
- int return_sequence_length = Assembler::kJSReturnSequenceLength;
- if (!masm_->ImmediateFitsAddrMode1Instruction(sp_delta)) {
- // Additional mov instruction generated.
- return_sequence_length++;
+#endif
+ // Make sure that the constant pool is not emitted inside of the return
+ // sequence.
+ { Assembler::BlockConstPoolScope block_const_pool(masm_);
+ // Here we use masm_-> instead of the __ macro to avoid the code coverage
+ // tool from instrumenting as we rely on the code size here.
+ int32_t sp_delta = (scope()->num_parameters() + 1) * kPointerSize;
+ CodeGenerator::RecordPositions(masm_, position);
+ __ RecordJSReturn();
+ masm_->mov(sp, fp);
+ masm_->ldm(ia_w, sp, fp.bit() | lr.bit());
+ masm_->add(sp, sp, Operand(sp_delta));
+ masm_->Jump(lr);
}
- masm_->BlockConstPoolFor(return_sequence_length);
- CodeGenerator::RecordPositions(masm_, position);
- __ RecordJSReturn();
- __ mov(sp, fp);
- __ ldm(ia_w, sp, fp.bit() | lr.bit());
- __ add(sp, sp, Operand(sp_delta));
- __ Jump(lr);
-
+#ifdef DEBUG
// Check that the size of the code used for returning matches what is
- // expected by the debugger. The add instruction above is an addressing
- // mode 1 instruction where there are restrictions on which immediate values
- // can be encoded in the instruction and which immediate values requires
- // use of an additional instruction for moving the immediate to a temporary
- // register.
- ASSERT_EQ(return_sequence_length,
- masm_->InstructionsGeneratedSince(&check_exit_codesize));
+ // expected by the debugger. If the sp_delts above cannot be encoded in the
+ // add instruction the add will generate two instructions.
+ int return_sequence_length =
+ masm_->InstructionsGeneratedSince(&check_exit_codesize);
+ CHECK(return_sequence_length == Assembler::kJSReturnSequenceLength ||
+ return_sequence_length == Assembler::kJSReturnSequenceLength + 1);
+#endif
}
}
@@ -1594,10 +1592,9 @@
// Inline smi case if we are in a loop.
Label stub_call, done;
+ int count_value = expr->op() == Token::INC ? 1 : -1;
if (loop_depth() > 0) {
- __ add(r0, r0, Operand(expr->op() == Token::INC
- ? Smi::FromInt(1)
- : Smi::FromInt(-1)));
+ __ add(r0, r0, Operand(Smi::FromInt(count_value)), SetCC);
__ b(vs, &stub_call);
// We could eliminate this smi check if we split the code at
// the first smi check before calling ToNumber.
@@ -1605,11 +1602,9 @@
__ b(eq, &done);
__ bind(&stub_call);
// Call stub. Undo operation first.
- __ sub(r0, r0, Operand(r1));
+ __ sub(r0, r0, Operand(Smi::FromInt(count_value)));
}
- __ mov(r1, Operand(expr->op() == Token::INC
- ? Smi::FromInt(1)
- : Smi::FromInt(-1)));
+ __ mov(r1, Operand(Smi::FromInt(count_value)));
GenericBinaryOpStub stub(Token::ADD, NO_OVERWRITE, r1, r0);
__ CallStub(&stub);
__ bind(&done);
diff --git a/src/arm/ic-arm.cc b/src/arm/ic-arm.cc
index 0ac4217..2b7dac6 100644
--- a/src/arm/ic-arm.cc
+++ b/src/arm/ic-arm.cc
@@ -27,7 +27,9 @@
#include "v8.h"
+#include "assembler-arm.h"
#include "codegen-inl.h"
+#include "disasm.h"
#include "ic-inl.h"
#include "runtime.h"
#include "stub-cache.h"
@@ -561,21 +563,72 @@
}
-// TODO(181): Implement map patching once loop nesting is tracked on the
-// ARM platform so we can generate inlined fast-case code loads in
-// loops.
-void LoadIC::ClearInlinedVersion(Address address) {}
-bool LoadIC::PatchInlinedLoad(Address address, Object* map, int offset) {
- return false;
+void LoadIC::ClearInlinedVersion(Address address) {
+ // Reset the map check of the inlined inobject property load (if present) to
+ // guarantee failure by holding an invalid map (the null value). The offset
+ // can be patched to anything.
+ PatchInlinedLoad(address, Heap::null_value(), 0);
}
+
+bool LoadIC::PatchInlinedLoad(Address address, Object* map, int offset) {
+ // If the instruction after the call site is not the pseudo instruction nop1
+ // then this is not related to an inlined in-object property load. The nop1
+ // instruction is located just after the call to the IC in the deferred code
+ // handling the miss in the inlined code. After the nop1 instruction there is
+ // a branch instruction for jumping back from the deferred code.
+ Address address_after_call = address + Assembler::kCallTargetAddressOffset;
+ Instr instr_after_call = Assembler::instr_at(address_after_call);
+ if (!Assembler::IsNop(instr_after_call, NAMED_PROPERTY_LOAD_INLINED)) {
+ return false;
+ }
+ ASSERT_EQ(0, RegisterAllocator::kNumRegisters);
+ Address address_after_nop1 = address_after_call + Assembler::kInstrSize;
+ Instr instr_after_nop1 = Assembler::instr_at(address_after_nop1);
+ ASSERT(Assembler::IsBranch(instr_after_nop1));
+
+ // Find the end of the inlined code for handling the load.
+ int b_offset =
+ Assembler::GetBranchOffset(instr_after_nop1) + Assembler::kPcLoadDelta;
+ ASSERT(b_offset < 0); // Jumping back from deferred code.
+ Address inline_end_address = address_after_nop1 + b_offset;
+
+ // Patch the offset of the property load instruction (ldr r0, [r1, #+XXX]).
+ // The immediate must be represenatble in 12 bits.
+ ASSERT((JSObject::kMaxInstanceSize - JSObject::kHeaderSize) < (1 << 12));
+ Address ldr_property_instr_address = inline_end_address - 4;
+ ASSERT(Assembler::IsLdrRegisterImmediate(
+ Assembler::instr_at(ldr_property_instr_address)));
+ Instr ldr_property_instr = Assembler::instr_at(ldr_property_instr_address);
+ ldr_property_instr = Assembler::SetLdrRegisterImmediateOffset(
+ ldr_property_instr, offset - kHeapObjectTag);
+ Assembler::instr_at_put(ldr_property_instr_address, ldr_property_instr);
+
+ // Indicate that code has changed.
+ CPU::FlushICache(ldr_property_instr_address, 1 * Assembler::kInstrSize);
+
+ // Patch the map check.
+ Address ldr_map_instr_address = inline_end_address - 16;
+ Assembler::set_target_address_at(ldr_map_instr_address,
+ reinterpret_cast<Address>(map));
+ return true;
+}
+
+
void KeyedLoadIC::ClearInlinedVersion(Address address) {}
+
+
bool KeyedLoadIC::PatchInlinedLoad(Address address, Object* map) {
return false;
}
+
void KeyedStoreIC::ClearInlinedVersion(Address address) {}
+
+
void KeyedStoreIC::RestoreInlinedVersion(Address address) {}
+
+
bool KeyedStoreIC::PatchInlinedStore(Address address, Object* map) {
return false;
}
@@ -656,8 +709,8 @@
__ b(ne, &check_pixel_array);
// Check that the key (index) is within bounds.
__ ldr(r3, FieldMemOperand(r1, Array::kLengthOffset));
- __ cmp(r0, Operand(r3));
- __ b(ge, &slow);
+ __ cmp(r0, r3);
+ __ b(hs, &slow);
// Fast case: Do the load.
__ add(r3, r1, Operand(FixedArray::kHeaderSize - kHeapObjectTag));
__ ldr(r0, MemOperand(r3, r0, LSL, kPointerSizeLog2));
diff --git a/src/arm/macro-assembler-arm.cc b/src/arm/macro-assembler-arm.cc
index 376c4fb..17d6a10 100644
--- a/src/arm/macro-assembler-arm.cc
+++ b/src/arm/macro-assembler-arm.cc
@@ -117,18 +117,19 @@
// ldr ip, [pc, #...]
// blx ip
- // The two instructions (ldr and blx) could be separated by a literal
+ // The two instructions (ldr and blx) could be separated by a constant
// pool and the code would still work. The issue comes from the
// patching code which expect the ldr to be just above the blx.
- BlockConstPoolFor(2);
- // Statement positions are expected to be recorded when the target
- // address is loaded. The mov method will automatically record
- // positions when pc is the target, since this is not the case here
- // we have to do it explicitly.
- WriteRecordedPositions();
+ { BlockConstPoolScope block_const_pool(this);
+ // Statement positions are expected to be recorded when the target
+ // address is loaded. The mov method will automatically record
+ // positions when pc is the target, since this is not the case here
+ // we have to do it explicitly.
+ WriteRecordedPositions();
- mov(ip, Operand(target, rmode), LeaveCC, cond);
- blx(ip, cond);
+ mov(ip, Operand(target, rmode), LeaveCC, cond);
+ blx(ip, cond);
+ }
ASSERT(kCallTargetAddressOffset == 2 * kInstrSize);
#else
diff --git a/src/arm/simulator-arm.cc b/src/arm/simulator-arm.cc
index 827a88f..b44a738 100644
--- a/src/arm/simulator-arm.cc
+++ b/src/arm/simulator-arm.cc
@@ -474,6 +474,96 @@
}
+static bool ICacheMatch(void* one, void* two) {
+ ASSERT((reinterpret_cast<intptr_t>(one) & CachePage::kPageMask) == 0);
+ ASSERT((reinterpret_cast<intptr_t>(two) & CachePage::kPageMask) == 0);
+ return one == two;
+}
+
+
+static uint32_t ICacheHash(void* key) {
+ return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(key)) >> 2;
+}
+
+
+static bool AllOnOnePage(uintptr_t start, int size) {
+ intptr_t start_page = (start & ~CachePage::kPageMask);
+ intptr_t end_page = ((start + size) & ~CachePage::kPageMask);
+ return start_page == end_page;
+}
+
+
+void Simulator::FlushICache(void* start_addr, size_t size) {
+ intptr_t start = reinterpret_cast<intptr_t>(start_addr);
+ int intra_line = (start & CachePage::kLineMask);
+ start -= intra_line;
+ size += intra_line;
+ size = ((size - 1) | CachePage::kLineMask) + 1;
+ int offset = (start & CachePage::kPageMask);
+ while (!AllOnOnePage(start, size - 1)) {
+ int bytes_to_flush = CachePage::kPageSize - offset;
+ FlushOnePage(start, bytes_to_flush);
+ start += bytes_to_flush;
+ size -= bytes_to_flush;
+ ASSERT_EQ(0, start & CachePage::kPageMask);
+ offset = 0;
+ }
+ if (size != 0) {
+ FlushOnePage(start, size);
+ }
+}
+
+
+CachePage* Simulator::GetCachePage(void* page) {
+ v8::internal::HashMap::Entry* entry = i_cache_->Lookup(page,
+ ICacheHash(page),
+ true);
+ if (entry->value == NULL) {
+ CachePage* new_page = new CachePage();
+ entry->value = new_page;
+ }
+ return reinterpret_cast<CachePage*>(entry->value);
+}
+
+
+// Flush from start up to and not including start + size.
+void Simulator::FlushOnePage(intptr_t start, int size) {
+ ASSERT(size <= CachePage::kPageSize);
+ ASSERT(AllOnOnePage(start, size - 1));
+ ASSERT((start & CachePage::kLineMask) == 0);
+ ASSERT((size & CachePage::kLineMask) == 0);
+ void* page = reinterpret_cast<void*>(start & (~CachePage::kPageMask));
+ int offset = (start & CachePage::kPageMask);
+ CachePage* cache_page = GetCachePage(page);
+ char* valid_bytemap = cache_page->ValidityByte(offset);
+ memset(valid_bytemap, CachePage::LINE_INVALID, size >> CachePage::kLineShift);
+}
+
+
+void Simulator::CheckICache(Instr* instr) {
+#ifdef DEBUG
+ intptr_t address = reinterpret_cast<intptr_t>(instr);
+ void* page = reinterpret_cast<void*>(address & (~CachePage::kPageMask));
+ void* line = reinterpret_cast<void*>(address & (~CachePage::kLineMask));
+ int offset = (address & CachePage::kPageMask);
+ CachePage* cache_page = GetCachePage(page);
+ char* cache_valid_byte = cache_page->ValidityByte(offset);
+ bool cache_hit = (*cache_valid_byte == CachePage::LINE_VALID);
+ char* cached_line = cache_page->CachedData(offset & ~CachePage::kLineMask);
+ if (cache_hit) {
+ // Check that the data in memory matches the contents of the I-cache.
+ CHECK(memcmp(reinterpret_cast<void*>(instr),
+ cache_page->CachedData(offset),
+ Instr::kInstrSize) == 0);
+ } else {
+ // Cache miss. Load memory into the cache.
+ memcpy(cached_line, line, CachePage::kLineLength);
+ *cache_valid_byte = CachePage::LINE_VALID;
+ }
+#endif
+}
+
+
// Create one simulator per thread and keep it in thread local storage.
static v8::internal::Thread::LocalStorageKey simulator_key;
@@ -489,7 +579,13 @@
}
+v8::internal::HashMap* Simulator::i_cache_ = NULL;
+
+
Simulator::Simulator() {
+ if (i_cache_ == NULL) {
+ i_cache_ = new v8::internal::HashMap(&ICacheMatch);
+ }
Initialize();
// Setup simulator support first. Some of this information is needed to
// setup the architecture state.
@@ -554,6 +650,9 @@
swi_instruction_((AL << 28) | (0xf << 24) | call_rt_redirected),
fp_return_(fp_return),
next_(list_) {
+ Simulator::current()->
+ FlushICache(reinterpret_cast<void*>(&swi_instruction_),
+ Instr::kInstrSize);
list_ = this;
}
@@ -2342,6 +2441,7 @@
// Executes the current instruction.
void Simulator::InstructionDecode(Instr* instr) {
+ CheckICache(instr);
pc_modified_ = false;
if (::v8::internal::FLAG_trace_sim) {
disasm::NameConverter converter;
@@ -2536,7 +2636,6 @@
return address;
}
-
} } // namespace assembler::arm
#endif // __arm__
diff --git a/src/arm/simulator-arm.h b/src/arm/simulator-arm.h
index 4ee9070..91614ea 100644
--- a/src/arm/simulator-arm.h
+++ b/src/arm/simulator-arm.h
@@ -89,11 +89,43 @@
#include "constants-arm.h"
+#include "hashmap.h"
namespace assembler {
namespace arm {
+class CachePage {
+ public:
+ static const int LINE_VALID = 0;
+ static const int LINE_INVALID = 1;
+
+ static const int kPageShift = 12;
+ static const int kPageSize = 1 << kPageShift;
+ static const int kPageMask = kPageSize - 1;
+ static const int kLineShift = 2; // The cache line is only 4 bytes right now.
+ static const int kLineLength = 1 << kLineShift;
+ static const int kLineMask = kLineLength - 1;
+
+ CachePage() {
+ memset(&validity_map_, LINE_INVALID, sizeof(validity_map_));
+ }
+
+ char* ValidityByte(int offset) {
+ return &validity_map_[offset >> kLineShift];
+ }
+
+ char* CachedData(int offset) {
+ return &data_[offset];
+ }
+
+ private:
+ char data_[kPageSize]; // The cached data.
+ static const int kValidityMapSize = kPageSize >> kLineShift;
+ char validity_map_[kValidityMapSize]; // One byte per line.
+};
+
+
class Simulator {
public:
friend class Debugger;
@@ -162,6 +194,9 @@
// Pop an address from the JS stack.
uintptr_t PopAddress();
+ // ICache checking.
+ static void FlushICache(void* start, size_t size);
+
private:
enum special_values {
// Known bad pc value to ensure that the simulator does not execute
@@ -239,6 +274,11 @@
// Executes one instruction.
void InstructionDecode(Instr* instr);
+ // ICache.
+ static void CheckICache(Instr* instr);
+ static void FlushOnePage(intptr_t start, int size);
+ static CachePage* GetCachePage(void* page);
+
// Runtime call support.
static void* RedirectExternalReference(void* external_function,
bool fp_return);
@@ -276,6 +316,9 @@
int icount_;
static bool initialized_;
+ // Icache simulation
+ static v8::internal::HashMap* i_cache_;
+
// Registered breakpoints.
Instr* break_pc_;
instr_t break_instr_;
diff --git a/src/arm/stub-cache-arm.cc b/src/arm/stub-cache-arm.cc
index e0e166c..1cb88b4 100644
--- a/src/arm/stub-cache-arm.cc
+++ b/src/arm/stub-cache-arm.cc
@@ -53,7 +53,7 @@
// Check that the key in the entry matches the name.
__ mov(ip, Operand(key_offset));
__ ldr(ip, MemOperand(ip, offset, LSL, 1));
- __ cmp(name, Operand(ip));
+ __ cmp(name, ip);
__ b(ne, &miss);
// Get the code entry from the cache.
diff --git a/src/arm/virtual-frame-arm.cc b/src/arm/virtual-frame-arm.cc
index a4c5484..3183c09 100644
--- a/src/arm/virtual-frame-arm.cc
+++ b/src/arm/virtual-frame-arm.cc
@@ -299,6 +299,12 @@
}
+void VirtualFrame::CallLoadIC(RelocInfo::Mode mode) {
+ Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize));
+ CallCodeObject(ic, mode, 0);
+}
+
+
void VirtualFrame::CallCodeObject(Handle<Code> code,
RelocInfo::Mode rmode,
int dropped_args) {
diff --git a/src/arm/virtual-frame-arm.h b/src/arm/virtual-frame-arm.h
index c5a7fbb..c378b58 100644
--- a/src/arm/virtual-frame-arm.h
+++ b/src/arm/virtual-frame-arm.h
@@ -308,6 +308,10 @@
InvokeJSFlags flag,
int arg_count);
+ // Call load IC. Receiver on stack and property name in r2. Result returned in
+ // r0.
+ void CallLoadIC(RelocInfo::Mode mode);
+
// Call into an IC stub given the number of arguments it removes
// from the stack. Register arguments to the IC stub are implicit,
// and depend on the type of IC stub.
diff --git a/src/builtins.cc b/src/builtins.cc
index 767820a..9850f55 100644
--- a/src/builtins.cc
+++ b/src/builtins.cc
@@ -348,15 +348,7 @@
// Technically in new space this write might be omitted (except for
// debug mode which iterates through the heap), but to play safer
// we still do it.
- if (to_trim == 1) {
- former_start[0] = Heap::raw_unchecked_one_pointer_filler_map();
- } else if (to_trim == 2) {
- former_start[0] = Heap::raw_unchecked_two_pointer_filler_map();
- } else {
- former_start[0] = Heap::raw_unchecked_byte_array_map();
- ByteArray* as_byte_array = reinterpret_cast<ByteArray*>(elms);
- as_byte_array->set_length(ByteArray::LengthFor(to_trim * kPointerSize));
- }
+ Heap::CreateFillerObjectAt(elms->address(), to_trim * kPointerSize);
former_start[to_trim] = Heap::fixed_array_map();
former_start[to_trim + 1] = reinterpret_cast<Object*>(len - to_trim);
diff --git a/src/codegen.cc b/src/codegen.cc
index 5bbf050..20ea41e 100644
--- a/src/codegen.cc
+++ b/src/codegen.cc
@@ -77,11 +77,13 @@
}
// Generate the code.
Comment cmnt(masm_, code->comment());
+ code->BeforeGenerate();
masm_->bind(code->entry_label());
code->SaveRegisters();
code->Generate();
code->RestoreRegisters();
masm_->jmp(code->exit_label());
+ code->AfterGenerate();
}
}
diff --git a/src/codegen.h b/src/codegen.h
index a42eb4a..6232dd4 100644
--- a/src/codegen.h
+++ b/src/codegen.h
@@ -212,6 +212,9 @@
void SaveRegisters();
void RestoreRegisters();
+ virtual void BeforeGenerate() { }
+ virtual void AfterGenerate() { }
+
protected:
MacroAssembler* masm_;
diff --git a/src/globals.h b/src/globals.h
index e3ef958..bef5e8e 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -50,15 +50,32 @@
#define V8_HOST_ARCH_MIPS 1
#define V8_HOST_ARCH_32_BIT 1
#else
-#error Your host architecture was not detected as supported by v8
+#error Host architecture was not detected as supported by v8
#endif
+// Check for supported combinations of host and target architectures.
+#if defined(V8_TARGET_ARCH_IA32) && !defined(V8_HOST_ARCH_IA32)
+#error Target architecture ia32 is only supported on ia32 host
+#endif
+#if defined(V8_TARGET_ARCH_X64) && !defined(V8_HOST_ARCH_X64)
+#error Target architecture x64 is only supported on x64 host
+#endif
+#if (defined(V8_TARGET_ARCH_ARM) && \
+ !(defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_ARM)))
+#error Target architecture arm is only supported on arm and ia32 host
+#endif
+#if (defined(V8_TARGET_ARCH_MIPS) && \
+ !(defined(V8_HOST_ARCH_IA32) || defined(V8_HOST_ARCH_MIPS)))
+#error Target architecture mips is only supported on mips and ia32 host
+#endif
+
+// Define unaligned read for the target architectures supporting it.
#if defined(V8_TARGET_ARCH_X64) || defined(V8_TARGET_ARCH_IA32)
#define V8_TARGET_CAN_READ_UNALIGNED 1
#elif V8_TARGET_ARCH_ARM
#elif V8_TARGET_ARCH_MIPS
#else
-#error Your target architecture is not supported by v8
+#error Target architecture is not supported by v8
#endif
// Support for alternative bool type. This is only enabled if the code is
diff --git a/src/heap.cc b/src/heap.cc
index eb47884..0cd1791 100644
--- a/src/heap.cc
+++ b/src/heap.cc
@@ -2188,9 +2188,11 @@
if (size == 0) return;
HeapObject* filler = HeapObject::FromAddress(addr);
if (size == kPointerSize) {
- filler->set_map(Heap::one_pointer_filler_map());
+ filler->set_map(one_pointer_filler_map());
+ } else if (size == 2 * kPointerSize) {
+ filler->set_map(two_pointer_filler_map());
} else {
- filler->set_map(Heap::byte_array_map());
+ filler->set_map(byte_array_map());
ByteArray::cast(filler)->set_length(ByteArray::LengthFor(size));
}
}
diff --git a/src/ia32/codegen-ia32.cc b/src/ia32/codegen-ia32.cc
index 5ab7a53..e1f2d66 100644
--- a/src/ia32/codegen-ia32.cc
+++ b/src/ia32/codegen-ia32.cc
@@ -154,8 +154,7 @@
#endif
// New scope to get automatic timing calculation.
- { // NOLINT
- HistogramTimerScope codegen_timer(&Counters::code_generation);
+ { HistogramTimerScope codegen_timer(&Counters::code_generation);
CodeGenState state(this);
// Entry:
@@ -1181,16 +1180,23 @@
case Token::SAR:
if (left.is_smi()) return TypeInfo::Smi();
// Result is a smi if we shift by a constant >= 1, otherwise an integer32.
+ // Shift amount is masked with 0x1F (ECMA standard 11.7.2).
return (right.is_constant() && right.handle()->IsSmi()
- && Smi::cast(*right.handle())->value() >= 1)
+ && (Smi::cast(*right.handle())->value() & 0x1F) >= 1)
? TypeInfo::Smi()
: TypeInfo::Integer32();
case Token::SHR:
- // Result is a smi if we shift by a constant >= 2, otherwise an integer32.
- return (right.is_constant() && right.handle()->IsSmi()
- && Smi::cast(*right.handle())->value() >= 2)
- ? TypeInfo::Smi()
- : TypeInfo::Integer32();
+ // Result is a smi if we shift by a constant >= 2, an integer32 if
+ // we shift by 1, and an unsigned 32-bit integer if we shift by 0.
+ if (right.is_constant() && right.handle()->IsSmi()) {
+ int shift_amount = Smi::cast(*right.handle())->value() & 0x1F;
+ if (shift_amount > 1) {
+ return TypeInfo::Smi();
+ } else if (shift_amount > 0) {
+ return TypeInfo::Integer32();
+ }
+ }
+ return TypeInfo::Number();
case Token::ADD:
if (operands_type.IsSmi()) {
// The Integer32 range is big enough to take the sum of any two Smis.
@@ -2773,11 +2779,7 @@
// number comparison in the stub if it was inlined.
CompareStub stub(cc, strict, nan_info, !inline_number_compare);
Result answer = frame_->CallStub(&stub, &left_side, &right_side);
- if (cc == equal) {
- __ test(answer.reg(), Operand(answer.reg()));
- } else {
- __ cmp(answer.reg(), 0);
- }
+ __ test(answer.reg(), Operand(answer.reg()));
answer.Unuse();
dest->Split(cc);
} else {
@@ -6551,17 +6553,23 @@
frame_->Push(Factory::undefined_value());
return;
}
- Handle<FixedArray> cache_obj(
- FixedArray::cast(jsfunction_result_caches->get(cache_id)));
Load(args->at(1));
Result key = frame_->Pop();
key.ToRegister();
Result cache = allocator()->Allocate();
- __ mov(cache.reg(), cache_obj);
+ ASSERT(cache.is_valid());
+ __ mov(cache.reg(), ContextOperand(esi, Context::GLOBAL_INDEX));
+ __ mov(cache.reg(),
+ FieldOperand(cache.reg(), GlobalObject::kGlobalContextOffset));
+ __ mov(cache.reg(),
+ ContextOperand(cache.reg(), Context::JSFUNCTION_RESULT_CACHES_INDEX));
+ __ mov(cache.reg(),
+ FieldOperand(cache.reg(), FixedArray::OffsetOfElementAt(cache_id)));
Result tmp = allocator()->Allocate();
+ ASSERT(tmp.is_valid());
DeferredSearchCache* deferred = new DeferredSearchCache(tmp.reg(),
cache.reg(),
@@ -7045,6 +7053,12 @@
(node->expression()->AsBinaryOperation() != NULL &&
node->expression()->AsBinaryOperation()->ResultOverwriteAllowed());
switch (op) {
+ case Token::NOT:
+ case Token::DELETE:
+ case Token::TYPEOF:
+ UNREACHABLE(); // handled above
+ break;
+
case Token::SUB: {
GenericUnaryOpStub stub(Token::SUB, overwrite);
Result operand = frame_->Pop();
@@ -7085,11 +7099,7 @@
__ not_(answer.reg());
continue_label.Bind(&answer);
- if (operand_info.IsInteger32()) {
- answer.set_type_info(TypeInfo::Integer32());
- } else {
- answer.set_type_info(TypeInfo::Number());
- }
+ answer.set_type_info(TypeInfo::Integer32());
frame_->Push(&answer);
}
break;
@@ -7119,8 +7129,6 @@
break;
}
default:
- // NOT, DELETE, TYPEOF, and VOID are handled outside the
- // switch.
UNREACHABLE();
}
}
@@ -9814,8 +9822,7 @@
__ j(zero, &runtime_call_clear_stack);
#ifdef DEBUG
// Check that the layout of cache elements match expectations.
- { // NOLINT - doesn't like a single brace on a line.
- TranscendentalCache::Element test_elem[2];
+ { TranscendentalCache::Element test_elem[2];
char* elem_start = reinterpret_cast<char*>(&test_elem[0]);
char* elem2_start = reinterpret_cast<char*>(&test_elem[1]);
char* elem_in0 = reinterpret_cast<char*>(&(test_elem[0].in[0]));
diff --git a/src/liveedit-debugger.js b/src/liveedit-debugger.js
index d2aee87..8fbff41 100644
--- a/src/liveedit-debugger.js
+++ b/src/liveedit-debugger.js
@@ -28,26 +28,52 @@
// LiveEdit feature implementation. The script should be executed after
// debug-debugger.js.
-// A LiveEdit namespace is declared inside a single function constructor.
+// A LiveEdit namespace. It contains functions that modifies JavaScript code
+// according to changes of script source (if possible).
+//
+// When new script source is put in, the difference is calculated textually,
+// in form of list of delete/add/change chunks. The functions that include
+// change chunk(s) get recompiled, or their enclosing functions are
+// recompiled instead.
+// If the function may not be recompiled (e.g. it was completely erased in new
+// version of the script) it remains unchanged, but the code that could
+// create a new instance of this function goes away. An old version of script
+// is created to back up this obsolete function.
+// All unchanged functions have their positions updated accordingly.
+//
+// LiveEdit namespace is declared inside a single function constructor.
Debug.LiveEdit = new function() {
- // Changes script text and recompiles all relevant functions if possible.
+ // Applies the change to the script.
// The change is always a substring (change_pos, change_pos + change_len)
// being replaced with a completely different string new_str.
- //
- // Only one function will have its Code changed in result of this function.
- // All nested functions (should they have any instances at the moment) are
- // left unchanged and re-linked to a newly created script instance
- // representing old version of the source. (Generally speaking,
- // during the change all nested functions are erased and completely different
- // set of nested functions are introduced.) All other functions just have
- // their positions updated.
+ // This API is a legacy and is obsolete.
//
// @param {Script} script that is being changed
// @param {Array} change_log a list that collects engineer-readable
// description of what happened.
function ApplyPatch(script, change_pos, change_len, new_str,
change_log) {
+ var old_source = script.source;
+
+ // Prepare new source string.
+ var new_source = old_source.substring(0, change_pos) +
+ new_str + old_source.substring(change_pos + change_len);
+
+ return ApplyPatchMultiChunk(script,
+ [ change_pos, change_pos + change_len, change_pos + new_str.length],
+ new_source, change_log);
+ }
+ // Function is public.
+ this.ApplyPatch = ApplyPatch;
+
+ // Forward declaration for minifier.
+ var FunctionStatus;
+
+ // Applies the change to the script.
+ // The change is in form of list of chunks encoded in a single array as
+ // a series of triplets (pos1_start, pos1_end, pos2_end)
+ function ApplyPatchMultiChunk(script, diff_array, new_source, change_log) {
// Fully compiles source string as a script. Returns Array of
// FunctionCompileInfo -- a descriptions of all functions of the script.
@@ -117,27 +143,6 @@
return compile_info;
}
- // Given a positions, finds a function that fully includes the entire
- // change.
- function FindChangedFunction(compile_info, offset, len) {
- // First condition: function should start before the change region.
- // Function #0 (whole-script function) always does, but we want
- // one, that is later in this list.
- var index = 0;
- while (index + 1 < compile_info.length &&
- compile_info[index + 1].start_position <= offset) {
- index++;
- }
- // Now we are at the last function that begins before the change
- // region. The function that covers entire change region is either
- // this function or the enclosing one.
- for (; compile_info[index].end_position < offset + len;
- index = compile_info[index].outer_index) {
- Assert(index != -1);
- }
- return index;
- }
-
// Variable forward declarations. Preprocessor "Minifier" needs them.
var old_compile_info;
var shared_infos;
@@ -156,34 +161,27 @@
// Replaces function's Code.
function PatchCode(new_info, shared_info) {
- %LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array);
+ if (shared_info) {
+ %LiveEditReplaceFunctionCode(new_info.raw_array, shared_info.raw_array);
+ change_log.push( {function_patched: new_info.function_name} );
+ } else {
+ change_log.push( {function_patched: new_info.function_name,
+ function_info_not_found: true} );
+ }
- change_log.push( {function_patched: new_info.function_name} );
}
- var change_len_old;
- var change_len_new;
- // Translate position in old version of script into position in new
- // version of script.
- function PosTranslator(old_pos) {
- if (old_pos <= change_pos) {
- return old_pos;
- }
- if (old_pos >= change_pos + change_len_old) {
- return old_pos + change_len_new - change_len_old;
- }
- return -1;
- }
-
- var position_change_array;
+
var position_patch_report;
- function PatchPositions(new_info, shared_info) {
+ function PatchPositions(old_info, shared_info) {
if (!shared_info) {
- // TODO(LiveEdit): explain what is happening.
+ // TODO(LiveEdit): function is not compiled yet or is already collected.
+ position_patch_report.push(
+ { name: old_info.function_name, info_not_found: true } );
return;
}
var breakpoint_position_update = %LiveEditPatchFunctionPositions(
- shared_info.raw_array, position_change_array);
+ shared_info.raw_array, diff_array);
for (var i = 0; i < breakpoint_position_update.length; i += 2) {
var new_pos = breakpoint_position_update[i];
var break_point_object = breakpoint_position_update[i + 1];
@@ -191,7 +189,7 @@
{ from: break_point_object.source_position(), to: new_pos } } );
break_point_object.updateSourcePosition(new_pos, script);
}
- position_patch_report.push( { name: new_info.function_name } );
+ position_patch_report.push( { name: old_info.function_name } );
}
var link_to_old_script_report;
@@ -199,22 +197,19 @@
// Makes a function associated with another instance of a script (the
// one representing its old version). This way the function still
// may access its own text.
- function LinkToOldScript(shared_info) {
- %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script);
-
- link_to_old_script_report.push( { name: shared_info.function_name } );
+ function LinkToOldScript(shared_info, old_info_node) {
+ if (shared_info) {
+ %LiveEditRelinkFunctionToScript(shared_info.raw_array, old_script);
+ link_to_old_script_report.push( { name: shared_info.function_name } );
+ } else {
+ link_to_old_script_report.push(
+ { name: old_info_node.info.function_name, not_found: true } );
+ }
}
-
-
+
var old_source = script.source;
- var change_len_old = change_len;
- var change_len_new = new_str.length;
-
- // Prepare new source string.
- var new_source = old_source.substring(0, change_pos) +
- new_str + old_source.substring(change_pos + change_len);
-
+
// Find all SharedFunctionInfo's that are compiled from this script.
var shared_raw_list = %LiveEditFindSharedFunctionInfosForScript(script);
@@ -234,94 +229,103 @@
} catch (e) {
throw new Failure("Failed to compile new version of script: " + e);
}
-
- // An index of a single function, that is going to have its code replaced.
- var function_being_patched =
- FindChangedFunction(old_compile_info, change_pos, change_len_old);
-
- // In old and new script versions function with a change should have the
- // same indexes.
- var function_being_patched2 =
- FindChangedFunction(new_compile_info, change_pos, change_len_new);
- Assert(function_being_patched == function_being_patched2,
- "inconsistent old/new compile info");
-
- // Check that function being patched has the same expectations in a new
- // version. Otherwise we cannot safely patch its behavior and should
- // choose the outer function instead.
- while (!CompareFunctionExpectations(
- old_compile_info[function_being_patched],
- new_compile_info[function_being_patched])) {
-
- Assert(old_compile_info[function_being_patched].outer_index ==
- new_compile_info[function_being_patched].outer_index);
- function_being_patched =
- old_compile_info[function_being_patched].outer_index;
- Assert(function_being_patched != -1);
+
+ var pos_translator = new PosTranslator(diff_array);
+
+ // Build tree structures for old and new versions of the script.
+ var root_old_node = BuildCodeInfoTree(old_compile_info);
+ var root_new_node = BuildCodeInfoTree(new_compile_info);
+
+ // Analyze changes.
+ MarkChangedFunctions(root_old_node, pos_translator.GetChunks());
+ FindCorrespondingFunctions(root_old_node, root_new_node);
+
+ // Prepare to-do lists.
+ var replace_code_list = new Array();
+ var link_to_old_script_list = new Array();
+ var update_positions_list = new Array();
+
+ function HarvestTodo(old_node) {
+ function CollectDamaged(node) {
+ link_to_old_script_list.push(node);
+ for (var i = 0; i < node.children.length; i++) {
+ CollectDamaged(node.children[i]);
+ }
+ }
+
+ if (old_node.status == FunctionStatus.DAMAGED) {
+ CollectDamaged(old_node);
+ return;
+ }
+ if (old_node.status == FunctionStatus.UNCHANGED) {
+ update_positions_list.push(old_node);
+ } else if (old_node.status == FunctionStatus.SOURCE_CHANGED) {
+ update_positions_list.push(old_node);
+ } else if (old_node.status == FunctionStatus.CHANGED) {
+ replace_code_list.push(old_node);
+ }
+ for (var i = 0; i < old_node.children.length; i++) {
+ HarvestTodo(old_node.children[i]);
+ }
}
-
+
+ HarvestTodo(root_old_node);
+
+ // Collect shared infos for functions whose code need to be patched.
+ var replaced_function_infos = new Array();
+ for (var i = 0; i < replace_code_list.length; i++) {
+ var info = FindFunctionInfo(replace_code_list[i].array_index);
+ if (info) {
+ replaced_function_infos.push(info);
+ }
+ }
+
// Check that function being patched is not currently on stack.
- CheckStackActivations(
- [ FindFunctionInfo(function_being_patched) ], change_log );
+ CheckStackActivations(replaced_function_infos, change_log);
+ // We haven't changed anything before this line yet.
// Committing all changes.
- var old_script_name = CreateNameForOldScript(script);
-
- // Update the script text and create a new script representing an old
- // version of the script.
- var old_script = %LiveEditReplaceScript(script, new_source,
- old_script_name);
-
- PatchCode(new_compile_info[function_being_patched],
- FindFunctionInfo(function_being_patched));
+
+ // Create old script if there are function linked to old version.
+ if (link_to_old_script_list.length > 0) {
+ var old_script_name = CreateNameForOldScript(script);
+
+ // Update the script text and create a new script representing an old
+ // version of the script.
+ var old_script = %LiveEditReplaceScript(script, new_source,
+ old_script_name);
+
+ var link_to_old_script_report = new Array();
+ change_log.push( { linked_to_old_script: link_to_old_script_report } );
+
+ // We need to link to old script all former nested functions.
+ for (var i = 0; i < link_to_old_script_list.length; i++) {
+ LinkToOldScript(
+ FindFunctionInfo(link_to_old_script_list[i].array_index),
+ link_to_old_script_list[i]);
+ }
+ }
+
+
+ for (var i = 0; i < replace_code_list.length; i++) {
+ PatchCode(replace_code_list[i].corresponding_node.info,
+ FindFunctionInfo(replace_code_list[i].array_index));
+ }
var position_patch_report = new Array();
change_log.push( {position_patched: position_patch_report} );
-
- var position_change_array = [ change_pos,
- change_pos + change_len_old,
- change_pos + change_len_new ];
-
- // Update positions of all outer functions (i.e. all functions, that
- // are partially below the function being patched).
- for (var i = new_compile_info[function_being_patched].outer_index;
- i != -1;
- i = new_compile_info[i].outer_index) {
- PatchPositions(new_compile_info[i], FindFunctionInfo(i));
- }
-
- // Update positions of all functions that are fully below the function
- // being patched.
- var old_next_sibling =
- old_compile_info[function_being_patched].next_sibling_index;
- var new_next_sibling =
- new_compile_info[function_being_patched].next_sibling_index;
-
- // We simply go over the tail of both old and new lists. Their tails should
- // have an identical structure.
- if (old_next_sibling == -1) {
- Assert(new_next_sibling == -1);
- } else {
- Assert(old_compile_info.length - old_next_sibling ==
- new_compile_info.length - new_next_sibling);
-
- for (var i = old_next_sibling, j = new_next_sibling;
- i < old_compile_info.length; i++, j++) {
- PatchPositions(new_compile_info[j], FindFunctionInfo(i));
- }
- }
-
- var link_to_old_script_report = new Array();
- change_log.push( { linked_to_old_script: link_to_old_script_report } );
-
- // We need to link to old script all former nested functions.
- for (var i = function_being_patched + 1; i < old_next_sibling; i++) {
- LinkToOldScript(FindFunctionInfo(i), old_script);
+
+ for (var i = 0; i < update_positions_list.length; i++) {
+ // TODO(LiveEdit): take into account wether it's source_changed or
+ // unchanged and whether positions changed at all.
+ PatchPositions(update_positions_list[i].info,
+ FindFunctionInfo(update_positions_list[i].array_index));
}
}
// Function is public.
- this.ApplyPatch = ApplyPatch;
+ this.ApplyPatchMultiChunk = ApplyPatchMultiChunk;
+
function Assert(condition, message) {
if (!condition) {
@@ -332,6 +336,296 @@
}
}
}
+
+ function DiffChunk(pos1, pos2, len1, len2) {
+ this.pos1 = pos1;
+ this.pos2 = pos2;
+ this.len1 = len1;
+ this.len2 = len2;
+ }
+
+ function PosTranslator(diff_array) {
+ var chunks = new Array();
+ var pos1 = 0;
+ var pos2 = 0;
+ for (var i = 0; i < diff_array.length; i += 3) {
+ pos2 += diff_array[i] - pos1 + pos2;
+ pos1 = diff_array[i];
+ chunks.push(new DiffChunk(pos1, pos2, diff_array[i + 1] - pos1,
+ diff_array[i + 2] - pos2));
+ pos1 = diff_array[i + 1];
+ pos2 = diff_array[i + 2];
+ }
+ this.chunks = chunks;
+ }
+ PosTranslator.prototype.GetChunks = function() {
+ return this.chunks;
+ }
+
+ PosTranslator.prototype.Translate = function(pos, inside_chunk_handler) {
+ var array = this.chunks;
+ if (array.length == 0 || pos < array[0]) {
+ return pos;
+ }
+ var chunk_index1 = 0;
+ var chunk_index2 = array.length - 1;
+
+ while (chunk_index1 < chunk_index2) {
+ var middle_index = (chunk_index1 + chunk_index2) / 2;
+ if (pos < array[middle_index + 1].pos1) {
+ chunk_index2 = middle_index;
+ } else {
+ chunk_index1 = middle_index + 1;
+ }
+ }
+ var chunk = array[chunk_index1];
+ if (pos >= chunk.pos1 + chunk.len1) {
+ return pos += chunk.pos2 + chunk.len2 - chunk.pos1 - chunk.len1;
+ }
+
+ if (!inside_chunk_handler) {
+ inside_chunk_handler = PosTranslator.default_inside_chunk_handler;
+ }
+ inside_chunk_handler(pos, chunk);
+ }
+
+ PosTranslator.default_inside_chunk_handler = function() {
+ Assert(false, "Cannot translate position in chaged area");
+ }
+
+ var FunctionStatus = {
+ // No change to function or its inner functions; however its positions
+ // in script may have been shifted.
+ UNCHANGED: "unchanged",
+ // The code of a function remains unchanged, but something happened inside
+ // some inner functions.
+ SOURCE_CHANGED: "source changed",
+ // The code of a function is changed or some nested function cannot be
+ // properly patched so this function must be recompiled.
+ CHANGED: "changed",
+ // Function is changed but cannot be patched.
+ DAMAGED: "damaged"
+ }
+
+ function CodeInfoTreeNode(code_info, children, array_index) {
+ this.info = code_info;
+ this.children = children;
+ // an index in array of compile_info
+ this.array_index = array_index;
+ this.parent = void(0);
+
+ this.status = FunctionStatus.UNCHANGED;
+ // Status explanation is used for debugging purposes and will be shown
+ // in user UI if some explanations are needed.
+ this.status_explanation = void(0);
+ this.new_start_pos = void(0);
+ this.new_end_pos = void(0);
+ this.corresponding_node = void(0);
+ }
+
+ // From array of function infos that is implicitly a tree creates
+ // an actual tree of functions in script.
+ function BuildCodeInfoTree(code_info_array) {
+ // Throughtout all function we iterate over input array.
+ var index = 0;
+
+ // Recursive function that builds a branch of tree.
+ function BuildNode() {
+ var my_index = index;
+ index++;
+ var child_array = new Array();
+ while (index < code_info_array.length &&
+ code_info_array[index].outer_index == my_index) {
+ child_array.push(BuildNode());
+ }
+ var node = new CodeInfoTreeNode(code_info_array[my_index], child_array,
+ my_index);
+ for (var i = 0; i < child_array.length; i++) {
+ child_array[i].parent = node;
+ }
+ return node;
+ }
+
+ var root = BuildNode();
+ Assert(index == code_info_array.length);
+ return root;
+ }
+
+ // Applies a list of the textual diff chunks onto the tree of functions.
+ // Determines status of each function (from unchanged to damaged). However
+ // children of unchanged functions are ignored.
+ function MarkChangedFunctions(code_info_tree, chunks) {
+
+ // A convenient interator over diff chunks that also translates
+ // positions from old to new in a current non-changed part of script.
+ var chunk_it = new function() {
+ var chunk_index = 0;
+ var pos_diff = 0;
+ this.current = function() { return chunks[chunk_index]; }
+ this.next = function() {
+ var chunk = chunks[chunk_index];
+ pos_diff = chunk.pos2 + chunk.len2 - (chunk.pos1 + chunk.len1);
+ chunk_index++;
+ }
+ this.done = function() { return chunk_index >= chunks.length; }
+ this.TranslatePos = function(pos) { return pos + pos_diff; }
+ };
+
+ // A recursive function that processes internals of a function and all its
+ // inner functions. Iterator chunk_it initially points to a chunk that is
+ // below function start.
+ function ProcessInternals(info_node) {
+ info_node.new_start_pos = chunk_it.TranslatePos(
+ info_node.info.start_position);
+ var child_index = 0;
+ var code_changed = false;
+ var source_changed = false;
+ // Simultaneously iterates over child functions and over chunks.
+ while (!chunk_it.done() &&
+ chunk_it.current().pos1 < info_node.info.end_position) {
+ if (child_index < info_node.children.length) {
+ var child = info_node.children[child_index];
+
+ if (child.info.end_position <= chunk_it.current().pos1) {
+ ProcessUnchangedChild(child);
+ child_index++;
+ continue;
+ } else if (child.info.start_position >=
+ chunk_it.current().pos1 + chunk_it.current().len1) {
+ code_changed = true;
+ chunk_it.next();
+ continue;
+ } else if (child.info.start_position <= chunk_it.current().pos1 &&
+ child.info.end_position >= chunk_it.current().pos1 +
+ chunk_it.current().len1) {
+ ProcessInternals(child);
+ source_changed = source_changed ||
+ ( child.status != FunctionStatus.UNCHANGED );
+ code_changed = code_changed ||
+ ( child.status == FunctionStatus.DAMAGED );
+ child_index++;
+ continue;
+ } else {
+ code_changed = true;
+ child.status = FunctionStatus.DAMAGED;
+ child.status_explanation =
+ "Text diff overlaps with function boundary";
+ child_index++;
+ continue;
+ }
+ } else {
+ if (chunk_it.current().pos1 + chunk_it.current().len1 <=
+ info_node.info.end_position) {
+ info_node.status = FunctionStatus.CHANGED;
+ chunk_it.next();
+ continue;
+ } else {
+ info_node.status = FunctionStatus.DAMAGED;
+ info_node.status_explanation =
+ "Text diff overlaps with function boundary";
+ return;
+ }
+ }
+ Assert("Unreachable", false);
+ }
+ while (child_index < info_node.children.length) {
+ var child = info_node.children[child_index];
+ ProcessUnchangedChild(child);
+ child_index++;
+ }
+ if (code_changed) {
+ info_node.status = FunctionStatus.CHANGED;
+ } else if (source_changed) {
+ info_node.status = FunctionStatus.SOURCE_CHANGED;
+ }
+ info_node.new_end_pos =
+ chunk_it.TranslatePos(info_node.info.end_position);
+ }
+
+ function ProcessUnchangedChild(node) {
+ node.new_start_pos = chunk_it.TranslatePos(node.info.start_position);
+ node.new_end_pos = chunk_it.TranslatePos(node.info.end_position);
+ }
+
+ ProcessInternals(code_info_tree);
+ }
+
+ // For ecah old function (if it is not damaged) tries to find a corresponding
+ // function in new script. Typically it should succeed (non-damaged functions
+ // by definition may only have changes inside their bodies). However there are
+ // reasons for corresponence not to be found; function with unmodified text
+ // in new script may become enclosed into other function; the innocent change
+ // inside function body may in fact be something like "} function B() {" that
+ // splits a function into 2 functions.
+ function FindCorrespondingFunctions(old_code_tree, new_code_tree) {
+
+ // A recursive function that tries to find a correspondence for all
+ // child functions and for their inner functions.
+ function ProcessChildren(old_node, new_node) {
+ var old_children = old_node.children;
+ var new_children = new_node.children;
+
+ var old_index = 0;
+ var new_index = 0;
+ while (old_index < old_children.length) {
+ if (old_children[old_index].status == FunctionStatus.DAMAGED) {
+ old_index++;
+ } else if (new_index < new_children.length) {
+ if (new_children[new_index].info.start_position <
+ old_children[old_index].new_start_pos) {
+ new_index++;
+ } else if (new_children[new_index].info.start_position ==
+ old_children[old_index].new_start_pos) {
+ if (new_children[new_index].info.end_position ==
+ old_children[old_index].new_end_pos) {
+ old_children[old_index].corresponding_node =
+ new_children[new_index];
+ if (old_children[old_index].status != FunctionStatus.UNCHANGED) {
+ ProcessChildren(old_children[old_index],
+ new_children[new_index]);
+ if (old_children[old_index].status == FunctionStatus.DAMAGED) {
+ old_node.status = FunctionStatus.CHANGED;
+ }
+ }
+ } else {
+ old_children[old_index].status = FunctionStatus.DAMAGED;
+ old_children[old_index].status_explanation =
+ "No corresponding function in new script found";
+ old_node.status = FunctionStatus.CHANGED;
+ }
+ new_index++;
+ old_index++;
+ } else {
+ old_children[old_index].status = FunctionStatus.DAMAGED;
+ old_children[old_index].status_explanation =
+ "No corresponding function in new script found";
+ old_node.status = FunctionStatus.CHANGED;
+ old_index++;
+ }
+ } else {
+ old_children[old_index].status = FunctionStatus.DAMAGED;
+ old_children[old_index].status_explanation =
+ "No corresponding function in new script found";
+ old_node.status = FunctionStatus.CHANGED;
+ old_index++;
+ }
+ }
+
+ if (old_node.status == FunctionStatus.CHANGED) {
+ if (!CompareFunctionExpectations(old_node.info, new_node.info)) {
+ old_node.status = FunctionStatus.DAMAGED;
+ old_node.status_explanation = "Changed code expectations";
+ }
+ }
+ }
+
+ ProcessChildren(old_code_tree, new_code_tree);
+
+ old_code_tree.corresponding_node = new_code_tree;
+ Assert(old_code_tree.status != FunctionStatus.DAMAGED,
+ "Script became damaged");
+ }
+
// An object describing function compilation details. Its index fields
// apply to indexes inside array that stores these objects.
@@ -469,13 +763,12 @@
// LiveEdit main entry point: changes a script text to a new string.
function SetScriptSource(script, new_source, change_log) {
var old_source = script.source;
- var diff = FindSimpleDiff(old_source, new_source);
- if (!diff) {
+ var diff = CompareStringsLinewise(old_source, new_source);
+ if (diff.length == 0) {
+ change_log.push( { empty_diff: true } );
return;
}
- ApplyPatch(script, diff.change_pos, diff.old_len,
- new_source.substring(diff.change_pos, diff.change_pos + diff.new_len),
- change_log);
+ ApplyPatchMultiChunk(script, diff, new_source, change_log);
}
// Function is public.
this.SetScriptSource = SetScriptSource;
diff --git a/src/liveedit.cc b/src/liveedit.cc
index 4b30b2a..f016fc8 100644
--- a/src/liveedit.cc
+++ b/src/liveedit.cc
@@ -346,7 +346,7 @@
// Stores compare result in JSArray. Each chunk is stored as 3 array elements:
-// (pos1, len1, len2).
+// (pos1_begin, pos1_end, pos2_end).
class LineArrayCompareOutput : public Compare::Output {
public:
LineArrayCompareOutput(LineEndsWrapper line_ends1, LineEndsWrapper line_ends2)
@@ -362,9 +362,9 @@
SetElement(array_, current_size_, Handle<Object>(Smi::FromInt(char_pos1)));
SetElement(array_, current_size_ + 1,
- Handle<Object>(Smi::FromInt(char_len1)));
+ Handle<Object>(Smi::FromInt(char_pos1 + char_len1)));
SetElement(array_, current_size_ + 2,
- Handle<Object>(Smi::FromInt(char_len2)));
+ Handle<Object>(Smi::FromInt(char_pos2 + char_len2)));
current_size_ += 3;
}
@@ -717,8 +717,8 @@
}
void VisitCodeTarget(RelocInfo* rinfo) {
- ASSERT(RelocInfo::IsCodeTarget(rinfo->rmode()));
- if (Code::GetCodeFromTargetAddress(rinfo->target_address()) == original_) {
+ if (RelocInfo::IsCodeTarget(rinfo->rmode()) &&
+ Code::GetCodeFromTargetAddress(rinfo->target_address()) == original_) {
reloc_infos_.Add(*rinfo);
}
}
diff --git a/src/liveedit.h b/src/liveedit.h
index 5c73313..47e09d1 100644
--- a/src/liveedit.h
+++ b/src/liveedit.h
@@ -111,7 +111,7 @@
};
// Compares 2 strings line-by-line and returns diff in form of array of
- // triplets (pos1, len1, len2) describing list of diff chunks.
+ // triplets (pos1, pos1_end, pos2_end) describing list of diff chunks.
static Handle<JSArray> CompareStringsLinewise(Handle<String> s1,
Handle<String> s2);
};
diff --git a/src/mark-compact.cc b/src/mark-compact.cc
index 80ad389..8421125 100644
--- a/src/mark-compact.cc
+++ b/src/mark-compact.cc
@@ -1055,6 +1055,7 @@
PageIterator it(space, PageIterator::PAGES_IN_USE);
while (it.has_next()) {
Page* p = it.next();
+
// The offset of each live object in the page from the first live object
// in the page.
int offset = 0;
@@ -1277,6 +1278,29 @@
static void SweepSpace(PagedSpace* space, DeallocateFunction dealloc) {
PageIterator it(space, PageIterator::PAGES_IN_USE);
+
+ // During sweeping of paged space we are trying to find longest sequences
+ // of pages without live objects and free them (instead of putting them on
+ // the free list).
+
+ // Page preceding current.
+ Page* prev = Page::FromAddress(NULL);
+
+ // First empty page in a sequence.
+ Page* first_empty_page = Page::FromAddress(NULL);
+
+ // Page preceding first empty page.
+ Page* prec_first_empty_page = Page::FromAddress(NULL);
+
+ // If last used page of space ends with a sequence of dead objects
+ // we can adjust allocation top instead of puting this free area into
+ // the free list. Thus during sweeping we keep track of such areas
+ // and defer their deallocation until the sweeping of the next page
+ // is done: if one of the next pages contains live objects we have
+ // to put such area into the free list.
+ Address last_free_start = NULL;
+ int last_free_size = 0;
+
while (it.has_next()) {
Page* p = it.next();
@@ -1291,8 +1315,9 @@
if (object->IsMarked()) {
object->ClearMark();
MarkCompactCollector::tracer()->decrement_marked_count();
+
if (!is_previous_alive) { // Transition from free to live.
- dealloc(free_start, static_cast<int>(current - free_start));
+ dealloc(free_start, static_cast<int>(current - free_start), true);
is_previous_alive = true;
}
} else {
@@ -1306,39 +1331,113 @@
// loop.
}
- // If the last region was not live we need to deallocate from
- // free_start to the allocation top in the page.
- if (!is_previous_alive) {
- int free_size = static_cast<int>(p->AllocationTop() - free_start);
- if (free_size > 0) {
- dealloc(free_start, free_size);
+ bool page_is_empty = (p->ObjectAreaStart() == p->AllocationTop())
+ || (!is_previous_alive && free_start == p->ObjectAreaStart());
+
+ if (page_is_empty) {
+ // This page is empty. Check whether we are in the middle of
+ // sequence of empty pages and start one if not.
+ if (!first_empty_page->is_valid()) {
+ first_empty_page = p;
+ prec_first_empty_page = prev;
+ }
+
+ if (!is_previous_alive) {
+ // There are dead objects on this page. Update space accounting stats
+ // without putting anything into free list.
+ int size_in_bytes = static_cast<int>(p->AllocationTop() - free_start);
+ if (size_in_bytes > 0) {
+ dealloc(free_start, size_in_bytes, false);
+ }
+ }
+ } else {
+ // This page is not empty. Sequence of empty pages ended on the previous
+ // one.
+ if (first_empty_page->is_valid()) {
+ space->FreePages(prec_first_empty_page, prev);
+ prec_first_empty_page = first_empty_page = Page::FromAddress(NULL);
+ }
+
+ // If there is a free ending area on one of the previous pages we have
+ // deallocate that area and put it on the free list.
+ if (last_free_size > 0) {
+ dealloc(last_free_start, last_free_size, true);
+ last_free_start = NULL;
+ last_free_size = 0;
+ }
+
+ // If the last region of this page was not live we remember it.
+ if (!is_previous_alive) {
+ ASSERT(last_free_size == 0);
+ last_free_size = static_cast<int>(p->AllocationTop() - free_start);
+ last_free_start = free_start;
}
}
+
+ prev = p;
+ }
+
+ // We reached end of space. See if we need to adjust allocation top.
+ Address new_allocation_top = NULL;
+
+ if (first_empty_page->is_valid()) {
+ // Last used pages in space are empty. We can move allocation top backwards
+ // to the beginning of first empty page.
+ ASSERT(prev == space->AllocationTopPage());
+
+ new_allocation_top = first_empty_page->ObjectAreaStart();
+ }
+
+ if (last_free_size > 0) {
+ // There was a free ending area on the previous page.
+ // Deallocate it without putting it into freelist and move allocation
+ // top to the beginning of this free area.
+ dealloc(last_free_start, last_free_size, false);
+ new_allocation_top = last_free_start;
+ }
+
+ if (new_allocation_top != NULL) {
+#ifdef DEBUG
+ Page* new_allocation_top_page = Page::FromAllocationTop(new_allocation_top);
+ if (!first_empty_page->is_valid()) {
+ ASSERT(new_allocation_top_page == space->AllocationTopPage());
+ } else if (last_free_size > 0) {
+ ASSERT(new_allocation_top_page == prec_first_empty_page);
+ } else {
+ ASSERT(new_allocation_top_page == first_empty_page);
+ }
+#endif
+
+ space->SetTop(new_allocation_top);
}
}
void MarkCompactCollector::DeallocateOldPointerBlock(Address start,
- int size_in_bytes) {
+ int size_in_bytes,
+ bool add_to_freelist) {
Heap::ClearRSetRange(start, size_in_bytes);
- Heap::old_pointer_space()->Free(start, size_in_bytes);
+ Heap::old_pointer_space()->Free(start, size_in_bytes, add_to_freelist);
}
void MarkCompactCollector::DeallocateOldDataBlock(Address start,
- int size_in_bytes) {
- Heap::old_data_space()->Free(start, size_in_bytes);
+ int size_in_bytes,
+ bool add_to_freelist) {
+ Heap::old_data_space()->Free(start, size_in_bytes, add_to_freelist);
}
void MarkCompactCollector::DeallocateCodeBlock(Address start,
- int size_in_bytes) {
- Heap::code_space()->Free(start, size_in_bytes);
+ int size_in_bytes,
+ bool add_to_freelist) {
+ Heap::code_space()->Free(start, size_in_bytes, add_to_freelist);
}
void MarkCompactCollector::DeallocateMapBlock(Address start,
- int size_in_bytes) {
+ int size_in_bytes,
+ bool add_to_freelist) {
// Objects in map space are assumed to have size Map::kSize and a
// valid map in their first word. Thus, we break the free block up into
// chunks and free them separately.
@@ -1346,13 +1445,14 @@
Heap::ClearRSetRange(start, size_in_bytes);
Address end = start + size_in_bytes;
for (Address a = start; a < end; a += Map::kSize) {
- Heap::map_space()->Free(a);
+ Heap::map_space()->Free(a, add_to_freelist);
}
}
void MarkCompactCollector::DeallocateCellBlock(Address start,
- int size_in_bytes) {
+ int size_in_bytes,
+ bool add_to_freelist) {
// Free-list elements in cell space are assumed to have a fixed size.
// We break the free block into chunks and add them to the free list
// individually.
@@ -1361,7 +1461,7 @@
Heap::ClearRSetRange(start, size_in_bytes);
Address end = start + size_in_bytes;
for (Address a = start; a < end; a += size) {
- Heap::cell_space()->Free(a);
+ Heap::cell_space()->Free(a, add_to_freelist);
}
}
diff --git a/src/mark-compact.h b/src/mark-compact.h
index 27335f2..3950e75 100644
--- a/src/mark-compact.h
+++ b/src/mark-compact.h
@@ -37,7 +37,11 @@
typedef bool (*IsAliveFunction)(HeapObject* obj, int* size, int* offset);
// Callback function for non-live blocks in the old generation.
-typedef void (*DeallocateFunction)(Address start, int size_in_bytes);
+// If add_to_freelist is false then just accounting stats are updated and
+// no attempt to add area to free list is made.
+typedef void (*DeallocateFunction)(Address start,
+ int size_in_bytes,
+ bool add_to_freelist);
// Forward declarations.
@@ -313,11 +317,25 @@
// Callback functions for deallocating non-live blocks in the old
// generation.
- static void DeallocateOldPointerBlock(Address start, int size_in_bytes);
- static void DeallocateOldDataBlock(Address start, int size_in_bytes);
- static void DeallocateCodeBlock(Address start, int size_in_bytes);
- static void DeallocateMapBlock(Address start, int size_in_bytes);
- static void DeallocateCellBlock(Address start, int size_in_bytes);
+ static void DeallocateOldPointerBlock(Address start,
+ int size_in_bytes,
+ bool add_to_freelist);
+
+ static void DeallocateOldDataBlock(Address start,
+ int size_in_bytes,
+ bool add_to_freelist);
+
+ static void DeallocateCodeBlock(Address start,
+ int size_in_bytes,
+ bool add_to_freelist);
+
+ static void DeallocateMapBlock(Address start,
+ int size_in_bytes,
+ bool add_to_freelist);
+
+ static void DeallocateCellBlock(Address start,
+ int size_in_bytes,
+ bool add_to_freelist);
// If we are not compacting the heap, we simply sweep the spaces except
// for the large object space, clearing mark bits and adding unmarked
diff --git a/src/regexp.js b/src/regexp.js
index 9929b11..24e3309 100644
--- a/src/regexp.js
+++ b/src/regexp.js
@@ -115,7 +115,9 @@
function DoRegExpExec(regexp, string, index) {
- return %_RegExpExec(regexp, string, index, lastMatchInfo);
+ var result = %_RegExpExec(regexp, string, index, lastMatchInfo);
+ if (result !== null) lastMatchInfoOverride = null;
+ return result;
}
@@ -136,7 +138,7 @@
function CloneRegExpResult(array) {
- if (array == null) return null;
+ if (array == null) return null;
var length = array.length;
var answer = %_RegExpConstructResult(length, array.index, array.input);
for (var i = 0; i < length; i++) {
@@ -237,7 +239,7 @@
cache.type = 'exec';
return matchIndices; // No match.
}
-
+ lastMatchInfoOverride = null;
var result = BuildResultFromMatchInfo(matchIndices, s);
if (this.global) {
@@ -312,7 +314,7 @@
cache.answer = false;
return false;
}
-
+ lastMatchInfoOverride = null;
if (this.global) this.lastIndex = lastMatchInfo[CAPTURE1];
cache.answer = true;
return true;
@@ -340,7 +342,9 @@
// on the captures array of the last successful match and the subject string
// of the last successful match.
function RegExpGetLastMatch() {
- if (lastMatchInfoOverride) { return lastMatchInfoOverride[0]; }
+ if (lastMatchInfoOverride !== null) {
+ return lastMatchInfoOverride[0];
+ }
var regExpSubject = LAST_SUBJECT(lastMatchInfo);
return SubString(regExpSubject,
lastMatchInfo[CAPTURE0],
diff --git a/src/runtime.cc b/src/runtime.cc
index ab77018..dd0a3d2 100644
--- a/src/runtime.cc
+++ b/src/runtime.cc
@@ -9766,7 +9766,7 @@
}
// Compares 2 strings line-by-line and returns diff in form of JSArray of
-// triplets (pos1, len1, len2) describing list of diff chunks.
+// triplets (pos1, pos1_end, pos2_end) describing list of diff chunks.
static Object* Runtime_LiveEditCompareStringsLinewise(Arguments args) {
ASSERT(args.length() == 2);
HandleScope scope;
diff --git a/src/serialize.cc b/src/serialize.cc
index 1c0b731..6841267 100644
--- a/src/serialize.cc
+++ b/src/serialize.cc
@@ -836,6 +836,9 @@
case START_NEW_PAGE_SERIALIZATION: {
int space = source_->Get();
pages_[space].Add(last_object_address_);
+ if (space == CODE_SPACE) {
+ CPU::FlushICache(last_object_address_, Page::kPageSize);
+ }
break;
}
case NATIVES_STRING_RESOURCE: {
diff --git a/src/spaces-inl.h b/src/spaces-inl.h
index 72f8305..66894c4 100644
--- a/src/spaces-inl.h
+++ b/src/spaces-inl.h
@@ -145,6 +145,40 @@
}
+bool Page::GetPageFlag(PageFlag flag) {
+ return (flags & flag) != 0;
+}
+
+
+void Page::SetPageFlag(PageFlag flag, bool value) {
+ if (value) {
+ flags |= flag;
+ } else {
+ flags &= ~flag;
+ }
+}
+
+
+bool Page::WasInUseBeforeMC() {
+ return GetPageFlag(WAS_IN_USE_BEFORE_MC);
+}
+
+
+void Page::SetWasInUseBeforeMC(bool was_in_use) {
+ SetPageFlag(WAS_IN_USE_BEFORE_MC, was_in_use);
+}
+
+
+bool Page::IsLargeObjectPage() {
+ return !GetPageFlag(IS_NORMAL_PAGE);
+}
+
+
+void Page::SetIsLargeObjectPage(bool is_large_object_page) {
+ SetPageFlag(IS_NORMAL_PAGE, !is_large_object_page);
+}
+
+
// -----------------------------------------------------------------------------
// MemoryAllocator
diff --git a/src/spaces.cc b/src/spaces.cc
index 5f191ed..6b6d926 100644
--- a/src/spaces.cc
+++ b/src/spaces.cc
@@ -524,7 +524,7 @@
for (int i = 0; i < pages_in_chunk; i++) {
Page* p = Page::FromAddress(page_addr);
p->opaque_header = OffsetFrom(page_addr + Page::kPageSize) | chunk_id;
- p->is_normal_page = 1;
+ p->SetIsLargeObjectPage(false);
page_addr += Page::kPageSize;
}
@@ -568,6 +568,15 @@
}
+void MemoryAllocator::FreeAllPages(PagedSpace* space) {
+ for (int i = 0, length = chunks_.length(); i < length; i++) {
+ if (chunks_[i].owner() == space) {
+ DeleteChunk(i);
+ }
+ }
+}
+
+
void MemoryAllocator::DeleteChunk(int chunk_id) {
ASSERT(IsValidChunk(chunk_id));
@@ -622,6 +631,74 @@
#endif
+void MemoryAllocator::RelinkPageListInChunkOrder(PagedSpace* space,
+ Page** first_page,
+ Page** last_page,
+ Page** last_page_in_use) {
+ Page* first = NULL;
+ Page* last = NULL;
+
+ for (int i = 0, length = chunks_.length(); i < length; i++) {
+ ChunkInfo& chunk = chunks_[i];
+
+ if (chunk.owner() == space) {
+ if (first == NULL) {
+ Address low = RoundUp(chunk.address(), Page::kPageSize);
+ first = Page::FromAddress(low);
+ }
+ last = RelinkPagesInChunk(i,
+ chunk.address(),
+ chunk.size(),
+ last,
+ last_page_in_use);
+ }
+ }
+
+ if (first_page != NULL) {
+ *first_page = first;
+ }
+
+ if (last_page != NULL) {
+ *last_page = last;
+ }
+}
+
+
+Page* MemoryAllocator::RelinkPagesInChunk(int chunk_id,
+ Address chunk_start,
+ size_t chunk_size,
+ Page* prev,
+ Page** last_page_in_use) {
+ Address page_addr = RoundUp(chunk_start, Page::kPageSize);
+ int pages_in_chunk = PagesInChunk(chunk_start, chunk_size);
+
+ if (prev->is_valid()) {
+ SetNextPage(prev, Page::FromAddress(page_addr));
+ }
+
+ for (int i = 0; i < pages_in_chunk; i++) {
+ Page* p = Page::FromAddress(page_addr);
+ p->opaque_header = OffsetFrom(page_addr + Page::kPageSize) | chunk_id;
+ page_addr += Page::kPageSize;
+
+ if (p->WasInUseBeforeMC()) {
+ *last_page_in_use = p;
+ }
+ }
+
+ // Set the next page of the last page to 0.
+ Page* last_page = Page::FromAddress(page_addr - Page::kPageSize);
+ last_page->opaque_header = OffsetFrom(0) | chunk_id;
+
+ if (last_page->WasInUseBeforeMC()) {
+ *last_page_in_use = last_page;
+ }
+
+ return last_page;
+}
+
+
+
// -----------------------------------------------------------------------------
// PagedSpace implementation
@@ -677,6 +754,8 @@
// Use first_page_ for allocation.
SetAllocationInfo(&allocation_info_, first_page_);
+ page_list_is_chunk_ordered_ = true;
+
return true;
}
@@ -687,9 +766,8 @@
void PagedSpace::TearDown() {
- first_page_ = MemoryAllocator::FreePages(first_page_);
- ASSERT(!first_page_->is_valid());
-
+ MemoryAllocator::FreeAllPages(this);
+ first_page_ = NULL;
accounting_stats_.Clear();
}
@@ -874,6 +952,12 @@
void PagedSpace::Shrink() {
+ if (!page_list_is_chunk_ordered_) {
+ // We can't shrink space if pages is not chunk-ordered
+ // (see comment for class MemoryAllocator for definition).
+ return;
+ }
+
// Release half of free pages.
Page* top_page = AllocationTopPage();
ASSERT(top_page->is_valid());
@@ -955,7 +1039,7 @@
// The next page will be above the allocation top.
above_allocation_top = true;
} else {
- ASSERT(top == current_page->ObjectAreaEnd() - page_extra_);
+ ASSERT(top == PageAllocationLimit(current_page));
}
// It should be packed with objects from the bottom to the top.
@@ -1363,7 +1447,7 @@
static void ReportCodeKindStatistics() {
- const char* table[Code::NUMBER_OF_KINDS];
+ const char* table[Code::NUMBER_OF_KINDS] = { NULL };
#define CASE(name) \
case Code::name: table[Code::name] = #name; \
@@ -1782,6 +1866,9 @@
// OldSpace implementation
void OldSpace::PrepareForMarkCompact(bool will_compact) {
+ // Call prepare of the super class.
+ PagedSpace::PrepareForMarkCompact(will_compact);
+
if (will_compact) {
// Reset relocation info. During a compacting collection, everything in
// the space is considered 'available' and we will rediscover live data
@@ -1852,6 +1939,112 @@
}
+void PagedSpace::FreePages(Page* prev, Page* last) {
+ if (last == AllocationTopPage()) {
+ // Pages are already at the end of used pages.
+ return;
+ }
+
+ Page* first = NULL;
+
+ // Remove pages from the list.
+ if (prev == NULL) {
+ first = first_page_;
+ first_page_ = last->next_page();
+ } else {
+ first = prev->next_page();
+ MemoryAllocator::SetNextPage(prev, last->next_page());
+ }
+
+ // Attach it after the last page.
+ MemoryAllocator::SetNextPage(last_page_, first);
+ last_page_ = last;
+ MemoryAllocator::SetNextPage(last, NULL);
+
+ // Clean them up.
+ do {
+ first->ClearRSet();
+ first = first->next_page();
+ } while (first != NULL);
+
+ // Order of pages in this space might no longer be consistent with
+ // order of pages in chunks.
+ page_list_is_chunk_ordered_ = false;
+}
+
+
+void PagedSpace::PrepareForMarkCompact(bool will_compact) {
+ if (will_compact) {
+ // MarkCompact collector relies on WAS_IN_USE_BEFORE_MC page flag
+ // to skip unused pages. Update flag value for all pages in space.
+ PageIterator all_pages_iterator(this, PageIterator::ALL_PAGES);
+ Page* last_in_use = AllocationTopPage();
+ bool in_use = true;
+
+ while (all_pages_iterator.has_next()) {
+ Page* p = all_pages_iterator.next();
+ p->SetWasInUseBeforeMC(in_use);
+ if (p == last_in_use) {
+ // We passed a page containing allocation top. All consequent
+ // pages are not used.
+ in_use = false;
+ }
+ }
+
+ if (!page_list_is_chunk_ordered_) {
+ Page* new_last_in_use = Page::FromAddress(NULL);
+ MemoryAllocator::RelinkPageListInChunkOrder(this,
+ &first_page_,
+ &last_page_,
+ &new_last_in_use);
+ ASSERT(new_last_in_use->is_valid());
+
+ if (new_last_in_use != last_in_use) {
+ // Current allocation top points to a page which is now in the middle
+ // of page list. We should move allocation top forward to the new last
+ // used page so various object iterators will continue to work properly.
+
+ int size_in_bytes = static_cast<int>(PageAllocationLimit(last_in_use) -
+ last_in_use->AllocationTop());
+
+ if (size_in_bytes > 0) {
+ // There is still some space left on this page. Create a fake
+ // object which will occupy all free space on this page.
+ // Otherwise iterators would not be able to scan this page
+ // correctly.
+
+ Heap::CreateFillerObjectAt(last_in_use->AllocationTop(),
+ size_in_bytes);
+ }
+
+ // New last in use page was in the middle of the list before
+ // sorting so it full.
+ SetTop(new_last_in_use->AllocationTop());
+
+ ASSERT(AllocationTopPage() == new_last_in_use);
+ ASSERT(AllocationTopPage()->WasInUseBeforeMC());
+ }
+
+ PageIterator pages_in_use_iterator(this, PageIterator::PAGES_IN_USE);
+ while (pages_in_use_iterator.has_next()) {
+ Page* p = pages_in_use_iterator.next();
+ if (!p->WasInUseBeforeMC()) {
+ // Empty page is in the middle of a sequence of used pages.
+ // Create a fake object which will occupy all free space on this page.
+ // Otherwise iterators would not be able to scan this page correctly.
+ int size_in_bytes = static_cast<int>(PageAllocationLimit(p) -
+ p->ObjectAreaStart());
+
+ Heap::CreateFillerObjectAt(p->ObjectAreaStart(), size_in_bytes);
+ }
+ }
+
+ page_list_is_chunk_ordered_ = true;
+ }
+ }
+}
+
+
bool PagedSpace::ReserveSpace(int bytes) {
Address limit = allocation_info_.limit;
Address top = allocation_info_.top;
@@ -2263,6 +2456,9 @@
// FixedSpace implementation
void FixedSpace::PrepareForMarkCompact(bool will_compact) {
+ // Call prepare of the super class.
+ PagedSpace::PrepareForMarkCompact(will_compact);
+
if (will_compact) {
// Reset relocation info.
MCResetRelocationInfo();
@@ -2360,7 +2556,7 @@
HeapObject* FixedSpace::AllocateInNextPage(Page* current_page,
int size_in_bytes) {
ASSERT(current_page->next_page()->is_valid());
- ASSERT(current_page->ObjectAreaEnd() - allocation_info_.top == page_extra_);
+ ASSERT(allocation_info_.top == PageAllocationLimit(current_page));
ASSERT_EQ(object_size_in_bytes_, size_in_bytes);
accounting_stats_.WasteBytes(page_extra_);
SetAllocationInfo(&allocation_info_, current_page->next_page());
@@ -2605,7 +2801,7 @@
// large object page. If the chunk_size happened to be written there, its
// low order bit should already be clear.
ASSERT((chunk_size & 0x1) == 0);
- page->is_normal_page &= ~0x1;
+ page->SetIsLargeObjectPage(true);
page->ClearRSet();
int extra_bytes = requested_size - object_size;
if (extra_bytes > 0) {
diff --git a/src/spaces.h b/src/spaces.h
index 1237bdf..df42d51 100644
--- a/src/spaces.h
+++ b/src/spaces.h
@@ -167,8 +167,17 @@
return 0 == (OffsetFrom(a) & kPageAlignmentMask);
}
+ // True if this page was in use before current compaction started.
+ // Result is valid only for pages owned by paged spaces and
+ // only after PagedSpace::PrepareForMarkCompact was called.
+ inline bool WasInUseBeforeMC();
+
+ inline void SetWasInUseBeforeMC(bool was_in_use);
+
// True if this page is a large object page.
- bool IsLargeObjectPage() { return (is_normal_page & 0x1) == 0; }
+ inline bool IsLargeObjectPage();
+
+ inline void SetIsLargeObjectPage(bool is_large_object_page);
// Returns the offset of a given address to this page.
INLINE(int Offset(Address a)) {
@@ -244,6 +253,14 @@
// Maximum object size that fits in a page.
static const int kMaxHeapObjectSize = kObjectAreaSize;
+ enum PageFlag {
+ IS_NORMAL_PAGE = 1 << 0,
+ WAS_IN_USE_BEFORE_MC = 1 << 1
+ };
+
+ inline bool GetPageFlag(PageFlag flag);
+ inline void SetPageFlag(PageFlag flag, bool value);
+
//---------------------------------------------------------------------------
// Page header description.
//
@@ -262,7 +279,8 @@
// second word *may* (if the page start and large object chunk start are
// the same) contain the large object chunk size. In either case, the
// low-order bit for large object pages will be cleared.
- int is_normal_page;
+ // For normal pages this word is used to store various page flags.
+ int flags;
// The following fields may overlap with remembered set, they can only
// be used in the mark-compact collector when remembered set is not
@@ -407,6 +425,13 @@
//
// The memory allocator also allocates chunks for the large object space, but
// they are managed by the space itself. The new space does not expand.
+//
+// The fact that pages for paged spaces are allocated and deallocated in chunks
+// induces a constraint on the order of pages in a linked lists. We say that
+// pages are linked in the chunk-order if and only if every two consecutive
+// pages from the same chunk are consecutive in the linked list.
+//
+
class MemoryAllocator : public AllStatic {
public:
@@ -466,13 +491,18 @@
static Page* AllocatePages(int requested_pages, int* allocated_pages,
PagedSpace* owner);
- // Frees pages from a given page and after. If 'p' is the first page
- // of a chunk, pages from 'p' are freed and this function returns an
- // invalid page pointer. Otherwise, the function searches a page
- // after 'p' that is the first page of a chunk. Pages after the
- // found page are freed and the function returns 'p'.
+ // Frees pages from a given page and after. Requires pages to be
+ // linked in chunk-order (see comment for class).
+ // If 'p' is the first page of a chunk, pages from 'p' are freed
+ // and this function returns an invalid page pointer.
+ // Otherwise, the function searches a page after 'p' that is
+ // the first page of a chunk. Pages after the found page
+ // are freed and the function returns 'p'.
static Page* FreePages(Page* p);
+ // Frees all pages owned by given space.
+ static void FreeAllPages(PagedSpace* space);
+
// Allocates and frees raw memory of certain size.
// These are just thin wrappers around OS::Allocate and OS::Free,
// but keep track of allocated bytes as part of heap.
@@ -511,6 +541,15 @@
static Page* FindFirstPageInSameChunk(Page* p);
static Page* FindLastPageInSameChunk(Page* p);
+ // Relinks list of pages owned by space to make it chunk-ordered.
+ // Returns new first and last pages of space.
+ // Also returns last page in relinked list which has WasInUsedBeforeMC
+ // flag set.
+ static void RelinkPageListInChunkOrder(PagedSpace* space,
+ Page** first_page,
+ Page** last_page,
+ Page** last_page_in_use);
+
#ifdef ENABLE_HEAP_PROTECTION
// Protect/unprotect a block of memory by marking it read-only/writable.
static inline void Protect(Address start, size_t size);
@@ -599,6 +638,12 @@
// used as a marking stack and its page headers are destroyed.
static Page* InitializePagesInChunk(int chunk_id, int pages_in_chunk,
PagedSpace* owner);
+
+ static Page* RelinkPagesInChunk(int chunk_id,
+ Address chunk_start,
+ size_t chunk_size,
+ Page* prev,
+ Page** last_page_in_use);
};
@@ -880,9 +925,16 @@
void ClearRSet();
// Prepares for a mark-compact GC.
- virtual void PrepareForMarkCompact(bool will_compact) = 0;
+ virtual void PrepareForMarkCompact(bool will_compact);
- virtual Address PageAllocationTop(Page* page) = 0;
+ // The top of allocation in a page in this space. Undefined if page is unused.
+ Address PageAllocationTop(Page* page) {
+ return page == TopPageOf(allocation_info_) ? top()
+ : PageAllocationLimit(page);
+ }
+
+ // The limit of allocation for a page in this space.
+ virtual Address PageAllocationLimit(Page* page) = 0;
// Current capacity without growing (Size() + Available() + Waste()).
int Capacity() { return accounting_stats_.Capacity(); }
@@ -920,6 +972,16 @@
// Used by ReserveSpace.
virtual void PutRestOfCurrentPageOnFreeList(Page* current_page) = 0;
+ // Free all pages in range from prev (exclusive) to last (inclusive).
+ // Freed pages are moved to the end of page list.
+ void FreePages(Page* prev, Page* last);
+
+ // Set space allocation info.
+ void SetTop(Address top) {
+ allocation_info_.top = top;
+ allocation_info_.limit = PageAllocationLimit(Page::FromAllocationTop(top));
+ }
+
// ---------------------------------------------------------------------------
// Mark-compact collection support functions
@@ -968,6 +1030,9 @@
static void ResetCodeStatistics();
#endif
+ // Returns the page of the allocation pointer.
+ Page* AllocationTopPage() { return TopPageOf(allocation_info_); }
+
protected:
// Maximum capacity of this space.
int max_capacity_;
@@ -982,6 +1047,10 @@
// Expand and Shrink.
Page* last_page_;
+ // True if pages owned by this space are linked in chunk-order.
+ // See comment for class MemoryAllocator for definition of chunk-order.
+ bool page_list_is_chunk_ordered_;
+
// Normal allocation information.
AllocationInfo allocation_info_;
@@ -1043,8 +1112,6 @@
void DoPrintRSet(const char* space_name);
#endif
private:
- // Returns the page of the allocation pointer.
- Page* AllocationTopPage() { return TopPageOf(allocation_info_); }
// Returns a pointer to the page of the relocation pointer.
Page* MCRelocationTopPage() { return TopPageOf(mc_forwarding_info_); }
@@ -1664,17 +1731,22 @@
// pointer).
int AvailableFree() { return free_list_.available(); }
- // The top of allocation in a page in this space. Undefined if page is unused.
- virtual Address PageAllocationTop(Page* page) {
- return page == TopPageOf(allocation_info_) ? top() : page->ObjectAreaEnd();
+ // The limit of allocation for a page in this space.
+ virtual Address PageAllocationLimit(Page* page) {
+ return page->ObjectAreaEnd();
}
// Give a block of memory to the space's free list. It might be added to
// the free list or accounted as waste.
- void Free(Address start, int size_in_bytes) {
- int wasted_bytes = free_list_.Free(start, size_in_bytes);
+ // If add_to_freelist is false then just accounting stats are updated and
+ // no attempt to add area to free list is made.
+ void Free(Address start, int size_in_bytes, bool add_to_freelist) {
accounting_stats_.DeallocateBytes(size_in_bytes);
- accounting_stats_.WasteBytes(wasted_bytes);
+
+ if (add_to_freelist) {
+ int wasted_bytes = free_list_.Free(start, size_in_bytes);
+ accounting_stats_.WasteBytes(wasted_bytes);
+ }
}
// Prepare for full garbage collection. Resets the relocation pointer and
@@ -1727,17 +1799,20 @@
page_extra_ = Page::kObjectAreaSize % object_size_in_bytes;
}
- // The top of allocation in a page in this space. Undefined if page is unused.
- virtual Address PageAllocationTop(Page* page) {
- return page == TopPageOf(allocation_info_) ? top()
- : page->ObjectAreaEnd() - page_extra_;
+ // The limit of allocation for a page in this space.
+ virtual Address PageAllocationLimit(Page* page) {
+ return page->ObjectAreaEnd() - page_extra_;
}
int object_size_in_bytes() { return object_size_in_bytes_; }
// Give a fixed sized block of memory to the space's free list.
- void Free(Address start) {
- free_list_.Free(start);
+ // If add_to_freelist is false then just accounting stats are updated and
+ // no attempt to add area to free list is made.
+ void Free(Address start, bool add_to_freelist) {
+ if (add_to_freelist) {
+ free_list_.Free(start);
+ }
accounting_stats_.DeallocateBytes(object_size_in_bytes_);
}
diff --git a/src/string.js b/src/string.js
index daa179b..9433249 100644
--- a/src/string.js
+++ b/src/string.js
@@ -175,9 +175,9 @@
// ECMA-262 section 15.5.4.10
function StringMatch(regexp) {
var subject = TO_STRING_INLINE(this);
- if (IS_REGEXP(regexp)) {
- if (!regexp.global) return regexp.exec(subject);
-
+ if (IS_REGEXP(regexp)) {
+ if (!regexp.global) return regexp.exec(subject);
+
var cache = regExpCache;
var saveAnswer = false;
@@ -435,63 +435,63 @@
// array to use in the future, or until the original is written back.
resultArray = $Array(16);
}
- try {
- // Must handle exceptions thrown by the replace functions correctly,
- // including unregistering global regexps.
- var res = %RegExpExecMultiple(regexp,
- subject,
- lastMatchInfo,
- resultArray);
- regexp.lastIndex = 0;
- if (IS_NULL(res)) {
- // No matches at all.
- return subject;
- }
- var len = res.length;
- var i = 0;
- if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) {
- var match_start = 0;
- while (i < len) {
- var elem = res[i];
- if (%_IsSmi(elem)) {
- if (elem > 0) {
- match_start = (elem >> 11) + (elem & 0x7ff);
- } else {
- match_start = res[++i] - elem;
- }
- } else {
- var func_result = replace.call(null, elem, match_start, subject);
- if (!IS_STRING(func_result)) {
- func_result = NonStringToString(func_result);
- }
- res[i] = func_result;
- match_start += elem.length;
- }
- i++;
- }
- } else {
- while (i < len) {
- var elem = res[i];
- if (!%_IsSmi(elem)) {
- // elem must be an Array.
- // Use the apply argument as backing for global RegExp properties.
- lastMatchInfoOverride = elem;
- var func_result = replace.apply(null, elem);
- if (!IS_STRING(func_result)) {
- func_result = NonStringToString(func_result);
- }
- res[i] = func_result;
- }
- i++;
- }
- }
- var result = new ReplaceResultBuilder(subject, res);
- return result.generate();
- } finally {
- lastMatchInfoOverride = null;
- resultArray.length = 0;
- reusableReplaceArray = resultArray;
+
+ var res = %RegExpExecMultiple(regexp,
+ subject,
+ lastMatchInfo,
+ resultArray);
+ regexp.lastIndex = 0;
+ if (IS_NULL(res)) {
+ // No matches at all.
+ return subject;
}
+ var len = res.length;
+ var i = 0;
+ if (NUMBER_OF_CAPTURES(lastMatchInfo) == 2) {
+ var match_start = 0;
+ var override = [null, 0, subject];
+ while (i < len) {
+ var elem = res[i];
+ if (%_IsSmi(elem)) {
+ if (elem > 0) {
+ match_start = (elem >> 11) + (elem & 0x7ff);
+ } else {
+ match_start = res[++i] - elem;
+ }
+ } else {
+ override[0] = elem;
+ override[1] = match_start;
+ lastMatchInfoOverride = override;
+ var func_result = replace.call(null, elem, match_start, subject);
+ if (!IS_STRING(func_result)) {
+ func_result = NonStringToString(func_result);
+ }
+ res[i] = func_result;
+ match_start += elem.length;
+ }
+ i++;
+ }
+ } else {
+ while (i < len) {
+ var elem = res[i];
+ if (!%_IsSmi(elem)) {
+ // elem must be an Array.
+ // Use the apply argument as backing for global RegExp properties.
+ lastMatchInfoOverride = elem;
+ var func_result = replace.apply(null, elem);
+ if (!IS_STRING(func_result)) {
+ func_result = NonStringToString(func_result);
+ }
+ res[i] = func_result;
+ }
+ i++;
+ }
+ }
+ var resultBuilder = new ReplaceResultBuilder(subject, res);
+ var result = resultBuilder.generate();
+ resultArray.length = 0;
+ reusableReplaceArray = resultArray;
+ return result;
} else { // Not a global regexp, no need to loop.
var matchInfo = DoRegExpExec(regexp, subject, 0);
if (IS_NULL(matchInfo)) return subject;
@@ -542,7 +542,6 @@
var s = TO_STRING_INLINE(this);
var match = DoRegExpExec(regexp, s, 0);
if (match) {
- lastMatchInfo = match;
return match[CAPTURE0];
}
return -1;
diff --git a/src/version.cc b/src/version.cc
index cca8ac9..bf1d92c 100644
--- a/src/version.cc
+++ b/src/version.cc
@@ -34,8 +34,8 @@
// cannot be changed without changing the SCons build script.
#define MAJOR_VERSION 2
#define MINOR_VERSION 2
-#define BUILD_NUMBER 4
-#define PATCH_LEVEL 2
+#define BUILD_NUMBER 5
+#define PATCH_LEVEL 0
#define CANDIDATE_VERSION false
// Define SONAME to have the SCons build the put a specific SONAME into the
diff --git a/src/x64/codegen-x64.cc b/src/x64/codegen-x64.cc
index a6aa36d..7a3278c 100644
--- a/src/x64/codegen-x64.cc
+++ b/src/x64/codegen-x64.cc
@@ -304,8 +304,7 @@
#endif
// New scope to get automatic timing calculation.
- { // NOLINT
- HistogramTimerScope codegen_timer(&Counters::code_generation);
+ { HistogramTimerScope codegen_timer(&Counters::code_generation);
CodeGenState state(this);
// Entry:
@@ -3118,6 +3117,7 @@
GenericUnaryOpStub stub(Token::SUB, overwrite);
Result operand = frame_->Pop();
Result answer = frame_->CallStub(&stub, &operand);
+ answer.set_type_info(TypeInfo::Number());
frame_->Push(&answer);
break;
}
@@ -3141,6 +3141,7 @@
frame_->Spill(answer.reg());
__ SmiNot(answer.reg(), answer.reg());
continue_label.Bind(&answer);
+ answer.set_type_info(TypeInfo::Smi());
frame_->Push(&answer);
break;
}
@@ -3149,6 +3150,7 @@
// Smi check.
JumpTarget continue_label;
Result operand = frame_->Pop();
+ TypeInfo operand_info = operand.type_info();
operand.ToRegister();
Condition is_smi = masm_->CheckSmi(operand.reg());
continue_label.Branch(is_smi, &operand);
@@ -3157,10 +3159,16 @@
CALL_FUNCTION, 1);
continue_label.Bind(&answer);
+ if (operand_info.IsSmi()) {
+ answer.set_type_info(TypeInfo::Smi());
+ } else if (operand_info.IsInteger32()) {
+ answer.set_type_info(TypeInfo::Integer32());
+ } else {
+ answer.set_type_info(TypeInfo::Number());
+ }
frame_->Push(&answer);
break;
}
-
default:
UNREACHABLE();
}
@@ -4297,17 +4305,23 @@
frame_->Push(Factory::undefined_value());
return;
}
- Handle<FixedArray> cache_obj(
- FixedArray::cast(jsfunction_result_caches->get(cache_id)));
Load(args->at(1));
Result key = frame_->Pop();
key.ToRegister();
Result cache = allocator()->Allocate();
- __ movq(cache.reg(), cache_obj, RelocInfo::EMBEDDED_OBJECT);
+ ASSERT(cache.is_valid());
+ __ movq(cache.reg(), ContextOperand(rsi, Context::GLOBAL_INDEX));
+ __ movq(cache.reg(),
+ FieldOperand(cache.reg(), GlobalObject::kGlobalContextOffset));
+ __ movq(cache.reg(),
+ ContextOperand(cache.reg(), Context::JSFUNCTION_RESULT_CACHES_INDEX));
+ __ movq(cache.reg(),
+ FieldOperand(cache.reg(), FixedArray::OffsetOfElementAt(cache_id)));
Result tmp = allocator()->Allocate();
+ ASSERT(tmp.is_valid());
DeferredSearchCache* deferred = new DeferredSearchCache(tmp.reg(),
cache.reg(),
@@ -5297,6 +5311,15 @@
}
+static bool CouldBeNaN(const Result& result) {
+ if (result.type_info().IsSmi()) return false;
+ if (result.type_info().IsInteger32()) return false;
+ if (!result.is_constant()) return true;
+ if (!result.handle()->IsHeapNumber()) return false;
+ return isnan(HeapNumber::cast(*result.handle())->value());
+}
+
+
void CodeGenerator::Comparison(AstNode* node,
Condition cc,
bool strict,
@@ -5614,15 +5637,29 @@
right_side.Unuse();
dest->Split(cc);
}
- } else { // Neither side is a constant Smi or null.
+ } else {
+ // Neither side is a constant Smi, constant 1-char string, or constant null.
// If either side is a non-smi constant, skip the smi check.
bool known_non_smi =
(left_side.is_constant() && !left_side.handle()->IsSmi()) ||
(right_side.is_constant() && !right_side.handle()->IsSmi());
+
+ NaNInformation nan_info =
+ (CouldBeNaN(left_side) && CouldBeNaN(right_side)) ?
+ kBothCouldBeNaN :
+ kCantBothBeNaN;
+
left_side.ToRegister();
right_side.ToRegister();
if (known_non_smi) {
+ // If at least one of the objects is not NaN, then if the objects
+ // are identical, they are equal.
+ if (nan_info == kCantBothBeNaN && cc == equal) {
+ __ cmpq(left_side.reg(), right_side.reg());
+ dest->true_target()->Branch(equal);
+ }
+
// When non-smi, call out to the compare stub.
CompareStub stub(cc, strict);
Result answer = frame_->CallStub(&stub, &left_side, &right_side);
@@ -5642,7 +5679,14 @@
Condition both_smi = masm_->CheckBothSmi(left_reg, right_reg);
is_smi.Branch(both_smi);
- // When non-smi, call out to the compare stub.
+ // When non-smi, call out to the compare stub, after inlined checks.
+ // If at least one of the objects is not NaN, then if the objects
+ // are identical, they are equal.
+ if (nan_info == kCantBothBeNaN && cc == equal) {
+ __ cmpq(left_side.reg(), right_side.reg());
+ dest->true_target()->Branch(equal);
+ }
+
CompareStub stub(cc, strict);
Result answer = frame_->CallStub(&stub, &left_side, &right_side);
__ SmiTest(answer.reg()); // Sets both zero and sign flags.
@@ -5691,6 +5735,57 @@
}
+static TypeInfo CalculateTypeInfo(TypeInfo operands_type,
+ Token::Value op,
+ const Result& right,
+ const Result& left) {
+ // Set TypeInfo of result according to the operation performed.
+ // We rely on the fact that smis have a 32 bit payload on x64.
+ STATIC_ASSERT(kSmiValueSize == 32);
+ switch (op) {
+ case Token::COMMA:
+ return right.type_info();
+ case Token::OR:
+ case Token::AND:
+ // Result type can be either of the two input types.
+ return operands_type;
+ case Token::BIT_OR:
+ case Token::BIT_XOR:
+ case Token::BIT_AND:
+ // Result is always a smi.
+ return TypeInfo::Smi();
+ case Token::SAR:
+ case Token::SHL:
+ // Result is always a smi.
+ return TypeInfo::Smi();
+ case Token::SHR:
+ // Result of x >>> y is always a smi if masked y >= 1, otherwise a number.
+ return (right.is_constant() && right.handle()->IsSmi()
+ && (Smi::cast(*right.handle())->value() & 0x1F) >= 1)
+ ? TypeInfo::Smi()
+ : TypeInfo::Number();
+ case Token::ADD:
+ if (operands_type.IsNumber()) {
+ return TypeInfo::Number();
+ } else if (left.type_info().IsString() || right.type_info().IsString()) {
+ return TypeInfo::String();
+ } else {
+ return TypeInfo::Unknown();
+ }
+ case Token::SUB:
+ case Token::MUL:
+ case Token::DIV:
+ case Token::MOD:
+ // Result is always a number.
+ return TypeInfo::Number();
+ default:
+ UNREACHABLE();
+ }
+ UNREACHABLE();
+ return TypeInfo::Unknown();
+}
+
+
void CodeGenerator::GenericBinaryOperation(Token::Value op,
StaticType* type,
OverwriteMode overwrite_mode) {
@@ -5756,6 +5851,8 @@
TypeInfo operands_type =
TypeInfo::Combine(left.type_info(), right.type_info());
+ TypeInfo result_type = CalculateTypeInfo(operands_type, op, right, left);
+
Result answer;
if (left_is_non_smi_constant || right_is_non_smi_constant) {
GenericBinaryOpStub stub(op,
@@ -5786,56 +5883,6 @@
}
}
- // Set TypeInfo of result according to the operation performed.
- // We rely on the fact that smis have a 32 bit payload on x64.
- ASSERT(kSmiValueSize == 32);
- TypeInfo result_type = TypeInfo::Unknown();
- switch (op) {
- case Token::COMMA:
- result_type = right.type_info();
- break;
- case Token::OR:
- case Token::AND:
- // Result type can be either of the two input types.
- result_type = operands_type;
- break;
- case Token::BIT_OR:
- case Token::BIT_XOR:
- case Token::BIT_AND:
- // Result is always a smi.
- result_type = TypeInfo::Smi();
- break;
- case Token::SAR:
- case Token::SHL:
- // Result is always a smi.
- result_type = TypeInfo::Smi();
- break;
- case Token::SHR:
- // Result of x >>> y is always a smi if y >= 1, otherwise a number.
- result_type = (right.is_constant() && right.handle()->IsSmi()
- && Smi::cast(*right.handle())->value() >= 1)
- ? TypeInfo::Smi()
- : TypeInfo::Number();
- break;
- case Token::ADD:
- if (operands_type.IsNumber()) {
- result_type = TypeInfo::Number();
- } else if (operands_type.IsString()) {
- result_type = TypeInfo::String();
- } else {
- result_type = TypeInfo::Unknown();
- }
- break;
- case Token::SUB:
- case Token::MUL:
- case Token::DIV:
- case Token::MOD:
- // Result is always a number.
- result_type = TypeInfo::Number();
- break;
- default:
- UNREACHABLE();
- }
answer.set_type_info(result_type);
frame_->Push(&answer);
}