| Index: src/arm/full-codegen-arm.cc
|
| ===================================================================
|
| --- src/arm/full-codegen-arm.cc (revision 7180)
|
| +++ src/arm/full-codegen-arm.cc (working copy)
|
| @@ -215,53 +215,57 @@
|
| // stack frame was an arguments adapter frame.
|
| ArgumentsAccessStub stub(ArgumentsAccessStub::NEW_OBJECT);
|
| __ CallStub(&stub);
|
| - // Duplicate the value; move-to-slot operation might clobber registers.
|
| - __ mov(r3, r0);
|
| - Move(arguments->AsSlot(), r0, r1, r2);
|
| - Slot* dot_arguments_slot = scope()->arguments_shadow()->AsSlot();
|
| - Move(dot_arguments_slot, r3, r1, r2);
|
| - }
|
|
|
| - { Comment cmnt(masm_, "[ Declarations");
|
| - // For named function expressions, declare the function name as a
|
| - // constant.
|
| - if (scope()->is_function_scope() && scope()->function() != NULL) {
|
| - EmitDeclaration(scope()->function(), Variable::CONST, NULL);
|
| + Variable* arguments_shadow = scope()->arguments_shadow();
|
| + if (arguments_shadow != NULL) {
|
| + // Duplicate the value; move-to-slot operation might clobber registers.
|
| + __ mov(r3, r0);
|
| + Move(arguments_shadow->AsSlot(), r3, r1, r2);
|
| }
|
| - // Visit all the explicit declarations unless there is an illegal
|
| - // redeclaration.
|
| - if (scope()->HasIllegalRedeclaration()) {
|
| - scope()->VisitIllegalRedeclaration(this);
|
| - } else {
|
| - VisitDeclarations(scope()->declarations());
|
| - }
|
| + Move(arguments->AsSlot(), r0, r1, r2);
|
| }
|
|
|
| if (FLAG_trace) {
|
| __ CallRuntime(Runtime::kTraceEnter, 0);
|
| }
|
|
|
| - // Check the stack for overflow or break request.
|
| - { Comment cmnt(masm_, "[ Stack check");
|
| - PrepareForBailout(info->function(), NO_REGISTERS);
|
| - Label ok;
|
| - __ LoadRoot(ip, Heap::kStackLimitRootIndex);
|
| - __ cmp(sp, Operand(ip));
|
| - __ b(hs, &ok);
|
| - StackCheckStub stub;
|
| - __ CallStub(&stub);
|
| - __ bind(&ok);
|
| - }
|
| + // Visit the declarations and body unless there is an illegal
|
| + // redeclaration.
|
| + if (scope()->HasIllegalRedeclaration()) {
|
| + Comment cmnt(masm_, "[ Declarations");
|
| + scope()->VisitIllegalRedeclaration(this);
|
|
|
| - { Comment cmnt(masm_, "[ Body");
|
| - ASSERT(loop_depth() == 0);
|
| - VisitStatements(function()->body());
|
| - ASSERT(loop_depth() == 0);
|
| + } else {
|
| + { Comment cmnt(masm_, "[ Declarations");
|
| + // For named function expressions, declare the function name as a
|
| + // constant.
|
| + if (scope()->is_function_scope() && scope()->function() != NULL) {
|
| + EmitDeclaration(scope()->function(), Variable::CONST, NULL);
|
| + }
|
| + VisitDeclarations(scope()->declarations());
|
| + }
|
| +
|
| + { Comment cmnt(masm_, "[ Stack check");
|
| + PrepareForBailout(info->function(), NO_REGISTERS);
|
| + Label ok;
|
| + __ LoadRoot(ip, Heap::kStackLimitRootIndex);
|
| + __ cmp(sp, Operand(ip));
|
| + __ b(hs, &ok);
|
| + StackCheckStub stub;
|
| + __ CallStub(&stub);
|
| + __ bind(&ok);
|
| + }
|
| +
|
| + { Comment cmnt(masm_, "[ Body");
|
| + ASSERT(loop_depth() == 0);
|
| + VisitStatements(function()->body());
|
| + ASSERT(loop_depth() == 0);
|
| + }
|
| }
|
|
|
| + // Always emit a 'return undefined' in case control fell off the end of
|
| + // the body.
|
| { Comment cmnt(masm_, "[ return <undefined>;");
|
| - // Emit a 'return undefined' in case control fell off the end of the
|
| - // body.
|
| __ LoadRoot(r0, Heap::kUndefinedValueRootIndex);
|
| }
|
| EmitReturnSequence();
|
| @@ -341,13 +345,6 @@
|
| }
|
|
|
|
|
| -FullCodeGenerator::ConstantOperand FullCodeGenerator::GetConstantOperand(
|
| - Token::Value op, Expression* left, Expression* right) {
|
| - ASSERT(ShouldInlineSmiCase(op));
|
| - return kNoConstants;
|
| -}
|
| -
|
| -
|
| void FullCodeGenerator::EffectContext::Plug(Slot* slot) const {
|
| }
|
|
|
| @@ -566,13 +563,38 @@
|
| void FullCodeGenerator::DoTest(Label* if_true,
|
| Label* if_false,
|
| Label* fall_through) {
|
| - // Call the runtime to find the boolean value of the source and then
|
| - // translate it into control flow to the pair of labels.
|
| - __ push(result_register());
|
| - __ CallRuntime(Runtime::kToBool, 1);
|
| - __ LoadRoot(ip, Heap::kTrueValueRootIndex);
|
| - __ cmp(r0, ip);
|
| - Split(eq, if_true, if_false, fall_through);
|
| + if (CpuFeatures::IsSupported(VFP3)) {
|
| + CpuFeatures::Scope scope(VFP3);
|
| + // Emit the inlined tests assumed by the stub.
|
| + __ LoadRoot(ip, Heap::kUndefinedValueRootIndex);
|
| + __ cmp(result_register(), ip);
|
| + __ b(eq, if_false);
|
| + __ LoadRoot(ip, Heap::kTrueValueRootIndex);
|
| + __ cmp(result_register(), ip);
|
| + __ b(eq, if_true);
|
| + __ LoadRoot(ip, Heap::kFalseValueRootIndex);
|
| + __ cmp(result_register(), ip);
|
| + __ b(eq, if_false);
|
| + STATIC_ASSERT(kSmiTag == 0);
|
| + __ tst(result_register(), result_register());
|
| + __ b(eq, if_false);
|
| + __ JumpIfSmi(result_register(), if_true);
|
| +
|
| + // Call the ToBoolean stub for all other cases.
|
| + ToBooleanStub stub(result_register());
|
| + __ CallStub(&stub);
|
| + __ tst(result_register(), result_register());
|
| + } else {
|
| + // Call the runtime to find the boolean value of the source and then
|
| + // translate it into control flow to the pair of labels.
|
| + __ push(result_register());
|
| + __ CallRuntime(Runtime::kToBool, 1);
|
| + __ LoadRoot(ip, Heap::kFalseValueRootIndex);
|
| + __ cmp(r0, ip);
|
| + }
|
| +
|
| + // The stub returns nonzero for true.
|
| + Split(ne, if_true, if_false, fall_through);
|
| }
|
|
|
|
|
| @@ -690,10 +712,11 @@
|
| // We bypass the general EmitSlotSearch because we know more about
|
| // this specific context.
|
|
|
| - // The variable in the decl always resides in the current context.
|
| + // The variable in the decl always resides in the current function
|
| + // context.
|
| ASSERT_EQ(0, scope()->ContextChainLength(variable->scope()));
|
| if (FLAG_debug_code) {
|
| - // Check if we have the correct context pointer.
|
| + // Check that we're not inside a 'with'.
|
| __ ldr(r1, ContextOperand(cp, Context::FCONTEXT_INDEX));
|
| __ cmp(r1, cp);
|
| __ Check(eq, "Unexpected declaration in current context.");
|
| @@ -765,7 +788,9 @@
|
| prop->key()->AsLiteral()->handle()->IsSmi());
|
| __ mov(r1, Operand(prop->key()->AsLiteral()->handle()));
|
|
|
| - Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Initialize));
|
| + Handle<Code> ic(Builtins::builtin(
|
| + is_strict_mode() ? Builtins::KeyedStoreIC_Initialize_Strict
|
| + : Builtins::KeyedStoreIC_Initialize));
|
| EmitCallIC(ic, RelocInfo::CODE_TARGET);
|
| // Value in r0 is ignored (declarations are statements).
|
| }
|
| @@ -781,10 +806,11 @@
|
| void FullCodeGenerator::DeclareGlobals(Handle<FixedArray> pairs) {
|
| // Call the runtime to declare the globals.
|
| // The context is the first argument.
|
| - __ mov(r1, Operand(pairs));
|
| - __ mov(r0, Operand(Smi::FromInt(is_eval() ? 1 : 0)));
|
| - __ Push(cp, r1, r0);
|
| - __ CallRuntime(Runtime::kDeclareGlobals, 3);
|
| + __ mov(r2, Operand(pairs));
|
| + __ mov(r1, Operand(Smi::FromInt(is_eval() ? 1 : 0)));
|
| + __ mov(r0, Operand(Smi::FromInt(strict_mode_flag())));
|
| + __ Push(cp, r2, r1, r0);
|
| + __ CallRuntime(Runtime::kDeclareGlobals, 4);
|
| // Return value is ignored.
|
| }
|
|
|
| @@ -793,9 +819,9 @@
|
| Comment cmnt(masm_, "[ SwitchStatement");
|
| Breakable nested_statement(this, stmt);
|
| SetStatementPosition(stmt);
|
| +
|
| // Keep the switch value on the stack until a case matches.
|
| VisitForStackValue(stmt->tag());
|
| -
|
| PrepareForBailoutForId(stmt->EntryId(), NO_REGISTERS);
|
|
|
| ZoneList<CaseClause*>* clauses = stmt->cases();
|
| @@ -861,6 +887,7 @@
|
| Comment cmnt(masm_, "[ Case body");
|
| CaseClause* clause = clauses->at(i);
|
| __ bind(clause->body_target()->entry_label());
|
| + PrepareForBailoutForId(clause->EntryId(), NO_REGISTERS);
|
| VisitStatements(clause->statements());
|
| }
|
|
|
| @@ -884,8 +911,9 @@
|
| __ LoadRoot(ip, Heap::kUndefinedValueRootIndex);
|
| __ cmp(r0, ip);
|
| __ b(eq, &exit);
|
| - __ LoadRoot(ip, Heap::kNullValueRootIndex);
|
| - __ cmp(r0, ip);
|
| + Register null_value = r5;
|
| + __ LoadRoot(null_value, Heap::kNullValueRootIndex);
|
| + __ cmp(r0, null_value);
|
| __ b(eq, &exit);
|
|
|
| // Convert the object to a JS object.
|
| @@ -899,12 +927,62 @@
|
| __ bind(&done_convert);
|
| __ push(r0);
|
|
|
| - // BUG(867): Check cache validity in generated code. This is a fast
|
| - // case for the JSObject::IsSimpleEnum cache validity checks. If we
|
| - // cannot guarantee cache validity, call the runtime system to check
|
| - // cache validity or get the property names in a fixed array.
|
| + // Check cache validity in generated code. This is a fast case for
|
| + // the JSObject::IsSimpleEnum cache validity checks. If we cannot
|
| + // guarantee cache validity, call the runtime system to check cache
|
| + // validity or get the property names in a fixed array.
|
| + Label next, call_runtime;
|
| + // Preload a couple of values used in the loop.
|
| + Register empty_fixed_array_value = r6;
|
| + __ LoadRoot(empty_fixed_array_value, Heap::kEmptyFixedArrayRootIndex);
|
| + Register empty_descriptor_array_value = r7;
|
| + __ LoadRoot(empty_descriptor_array_value,
|
| + Heap::kEmptyDescriptorArrayRootIndex);
|
| + __ mov(r1, r0);
|
| + __ bind(&next);
|
|
|
| + // Check that there are no elements. Register r1 contains the
|
| + // current JS object we've reached through the prototype chain.
|
| + __ ldr(r2, FieldMemOperand(r1, JSObject::kElementsOffset));
|
| + __ cmp(r2, empty_fixed_array_value);
|
| + __ b(ne, &call_runtime);
|
| +
|
| + // Check that instance descriptors are not empty so that we can
|
| + // check for an enum cache. Leave the map in r2 for the subsequent
|
| + // prototype load.
|
| + __ ldr(r2, FieldMemOperand(r1, HeapObject::kMapOffset));
|
| + __ ldr(r3, FieldMemOperand(r2, Map::kInstanceDescriptorsOffset));
|
| + __ cmp(r3, empty_descriptor_array_value);
|
| + __ b(eq, &call_runtime);
|
| +
|
| + // Check that there is an enum cache in the non-empty instance
|
| + // descriptors (r3). This is the case if the next enumeration
|
| + // index field does not contain a smi.
|
| + __ ldr(r3, FieldMemOperand(r3, DescriptorArray::kEnumerationIndexOffset));
|
| + __ JumpIfSmi(r3, &call_runtime);
|
| +
|
| + // For all objects but the receiver, check that the cache is empty.
|
| + Label check_prototype;
|
| + __ cmp(r1, r0);
|
| + __ b(eq, &check_prototype);
|
| + __ ldr(r3, FieldMemOperand(r3, DescriptorArray::kEnumCacheBridgeCacheOffset));
|
| + __ cmp(r3, empty_fixed_array_value);
|
| + __ b(ne, &call_runtime);
|
| +
|
| + // Load the prototype from the map and loop if non-null.
|
| + __ bind(&check_prototype);
|
| + __ ldr(r1, FieldMemOperand(r2, Map::kPrototypeOffset));
|
| + __ cmp(r1, null_value);
|
| + __ b(ne, &next);
|
| +
|
| + // The enum cache is valid. Load the map of the object being
|
| + // iterated over and use the cache for the iteration.
|
| + Label use_cache;
|
| + __ ldr(r0, FieldMemOperand(r0, HeapObject::kMapOffset));
|
| + __ b(&use_cache);
|
| +
|
| // Get the set of properties to enumerate.
|
| + __ bind(&call_runtime);
|
| __ push(r0); // Duplicate the enumerable object on the stack.
|
| __ CallRuntime(Runtime::kGetPropertyNamesFast, 1);
|
|
|
| @@ -919,6 +997,7 @@
|
| __ b(ne, &fixed_array);
|
|
|
| // We got a map in register r0. Get the enumeration cache from it.
|
| + __ bind(&use_cache);
|
| __ ldr(r1, FieldMemOperand(r0, Map::kInstanceDescriptorsOffset));
|
| __ ldr(r1, FieldMemOperand(r1, DescriptorArray::kEnumerationIndexOffset));
|
| __ ldr(r2, FieldMemOperand(r1, DescriptorArray::kEnumCacheBridgeCacheOffset));
|
| @@ -1007,8 +1086,14 @@
|
| void FullCodeGenerator::EmitNewClosure(Handle<SharedFunctionInfo> info,
|
| bool pretenure) {
|
| // Use the fast case closure allocation code that allocates in new
|
| - // space for nested functions that don't need literals cloning.
|
| - if (scope()->is_function_scope() &&
|
| + // space for nested functions that don't need literals cloning. If
|
| + // we're running with the --always-opt or the --prepare-always-opt
|
| + // flag, we need to use the runtime function so that the new function
|
| + // we are creating here gets a chance to have its code optimized and
|
| + // doesn't just get a copy of the existing unoptimized code.
|
| + if (!FLAG_always_opt &&
|
| + !FLAG_prepare_always_opt &&
|
| + scope()->is_function_scope() &&
|
| info->num_literals() == 0 &&
|
| !pretenure) {
|
| FastNewClosureStub stub;
|
| @@ -1036,7 +1121,7 @@
|
| Slot* slot,
|
| Label* slow) {
|
| ASSERT(slot->type() == Slot::CONTEXT);
|
| - Register current = cp;
|
| + Register context = cp;
|
| Register next = r3;
|
| Register temp = r4;
|
|
|
| @@ -1044,22 +1129,25 @@
|
| if (s->num_heap_slots() > 0) {
|
| if (s->calls_eval()) {
|
| // Check that extension is NULL.
|
| - __ ldr(temp, ContextOperand(current, Context::EXTENSION_INDEX));
|
| + __ ldr(temp, ContextOperand(context, Context::EXTENSION_INDEX));
|
| __ tst(temp, temp);
|
| __ b(ne, slow);
|
| }
|
| - __ ldr(next, ContextOperand(current, Context::CLOSURE_INDEX));
|
| + __ ldr(next, ContextOperand(context, Context::CLOSURE_INDEX));
|
| __ ldr(next, FieldMemOperand(next, JSFunction::kContextOffset));
|
| // Walk the rest of the chain without clobbering cp.
|
| - current = next;
|
| + context = next;
|
| }
|
| }
|
| // Check that last extension is NULL.
|
| - __ ldr(temp, ContextOperand(current, Context::EXTENSION_INDEX));
|
| + __ ldr(temp, ContextOperand(context, Context::EXTENSION_INDEX));
|
| __ tst(temp, temp);
|
| __ b(ne, slow);
|
| - __ ldr(temp, ContextOperand(current, Context::FCONTEXT_INDEX));
|
| - return ContextOperand(temp, slot->index());
|
| +
|
| + // This function is used only for loads, not stores, so it's safe to
|
| + // return an cp-based operand (the write barrier cannot be allowed to
|
| + // destroy the cp register).
|
| + return ContextOperand(context, slot->index());
|
| }
|
|
|
|
|
| @@ -1259,18 +1347,19 @@
|
| Comment cmnt(masm_, "[ RegExpLiteral");
|
| Label materialized;
|
| // Registers will be used as follows:
|
| + // r5 = materialized value (RegExp literal)
|
| // r4 = JS function, literals array
|
| // r3 = literal index
|
| // r2 = RegExp pattern
|
| // r1 = RegExp flags
|
| - // r0 = temp + materialized value (RegExp literal)
|
| + // r0 = RegExp literal clone
|
| __ ldr(r0, MemOperand(fp, JavaScriptFrameConstants::kFunctionOffset));
|
| __ ldr(r4, FieldMemOperand(r0, JSFunction::kLiteralsOffset));
|
| int literal_offset =
|
| FixedArray::kHeaderSize + expr->literal_index() * kPointerSize;
|
| - __ ldr(r0, FieldMemOperand(r4, literal_offset));
|
| + __ ldr(r5, FieldMemOperand(r4, literal_offset));
|
| __ LoadRoot(ip, Heap::kUndefinedValueRootIndex);
|
| - __ cmp(r0, ip);
|
| + __ cmp(r5, ip);
|
| __ b(ne, &materialized);
|
|
|
| // Create regexp literal using runtime function.
|
| @@ -1280,20 +1369,27 @@
|
| __ mov(r1, Operand(expr->flags()));
|
| __ Push(r4, r3, r2, r1);
|
| __ CallRuntime(Runtime::kMaterializeRegExpLiteral, 4);
|
| + __ mov(r5, r0);
|
|
|
| __ bind(&materialized);
|
| int size = JSRegExp::kSize + JSRegExp::kInObjectFieldCount * kPointerSize;
|
| - __ push(r0);
|
| + Label allocated, runtime_allocate;
|
| + __ AllocateInNewSpace(size, r0, r2, r3, &runtime_allocate, TAG_OBJECT);
|
| + __ jmp(&allocated);
|
| +
|
| + __ bind(&runtime_allocate);
|
| + __ push(r5);
|
| __ mov(r0, Operand(Smi::FromInt(size)));
|
| __ push(r0);
|
| __ CallRuntime(Runtime::kAllocateInNewSpace, 1);
|
| + __ pop(r5);
|
|
|
| + __ bind(&allocated);
|
| // After this, registers are used as follows:
|
| // r0: Newly allocated regexp.
|
| - // r1: Materialized regexp.
|
| + // r5: Materialized regexp.
|
| // r2: temp.
|
| - __ pop(r1);
|
| - __ CopyFields(r0, r1, r2.bit(), size / kPointerSize);
|
| + __ CopyFields(r0, r5, r2.bit(), size / kPointerSize);
|
| context()->Plug(r0);
|
| }
|
|
|
| @@ -1359,7 +1455,9 @@
|
| VisitForStackValue(key);
|
| VisitForStackValue(value);
|
| if (property->emit_store()) {
|
| - __ CallRuntime(Runtime::kSetProperty, 3);
|
| + __ mov(r0, Operand(Smi::FromInt(NONE))); // PropertyAttributes
|
| + __ push(r0);
|
| + __ CallRuntime(Runtime::kSetProperty, 4);
|
| } else {
|
| __ Drop(3);
|
| }
|
| @@ -1539,14 +1637,8 @@
|
| }
|
|
|
| Token::Value op = expr->binary_op();
|
| - ConstantOperand constant = ShouldInlineSmiCase(op)
|
| - ? GetConstantOperand(op, expr->target(), expr->value())
|
| - : kNoConstants;
|
| - ASSERT(constant == kRightConstant || constant == kNoConstants);
|
| - if (constant == kNoConstants) {
|
| - __ push(r0); // Left operand goes on the stack.
|
| - VisitForAccumulatorValue(expr->value());
|
| - }
|
| + __ push(r0); // Left operand goes on the stack.
|
| + VisitForAccumulatorValue(expr->value());
|
|
|
| OverwriteMode mode = expr->value()->ResultOverwriteAllowed()
|
| ? OVERWRITE_RIGHT
|
| @@ -1558,8 +1650,7 @@
|
| op,
|
| mode,
|
| expr->target(),
|
| - expr->value(),
|
| - constant);
|
| + expr->value());
|
| } else {
|
| EmitBinaryOp(op, mode);
|
| }
|
| @@ -1612,11 +1703,99 @@
|
| void FullCodeGenerator::EmitInlineSmiBinaryOp(Expression* expr,
|
| Token::Value op,
|
| OverwriteMode mode,
|
| - Expression* left,
|
| - Expression* right,
|
| - ConstantOperand constant) {
|
| - ASSERT(constant == kNoConstants); // Only handled case.
|
| - EmitBinaryOp(op, mode);
|
| + Expression* left_expr,
|
| + Expression* right_expr) {
|
| + Label done, smi_case, stub_call;
|
| +
|
| + Register scratch1 = r2;
|
| + Register scratch2 = r3;
|
| +
|
| + // Get the arguments.
|
| + Register left = r1;
|
| + Register right = r0;
|
| + __ pop(left);
|
| +
|
| + // Perform combined smi check on both operands.
|
| + __ orr(scratch1, left, Operand(right));
|
| + STATIC_ASSERT(kSmiTag == 0);
|
| + JumpPatchSite patch_site(masm_);
|
| + patch_site.EmitJumpIfSmi(scratch1, &smi_case);
|
| +
|
| + __ bind(&stub_call);
|
| + TypeRecordingBinaryOpStub stub(op, mode);
|
| + EmitCallIC(stub.GetCode(), &patch_site);
|
| + __ jmp(&done);
|
| +
|
| + __ bind(&smi_case);
|
| + // Smi case. This code works the same way as the smi-smi case in the type
|
| + // recording binary operation stub, see
|
| + // TypeRecordingBinaryOpStub::GenerateSmiSmiOperation for comments.
|
| + switch (op) {
|
| + case Token::SAR:
|
| + __ b(&stub_call);
|
| + __ GetLeastBitsFromSmi(scratch1, right, 5);
|
| + __ mov(right, Operand(left, ASR, scratch1));
|
| + __ bic(right, right, Operand(kSmiTagMask));
|
| + break;
|
| + case Token::SHL: {
|
| + __ b(&stub_call);
|
| + __ SmiUntag(scratch1, left);
|
| + __ GetLeastBitsFromSmi(scratch2, right, 5);
|
| + __ mov(scratch1, Operand(scratch1, LSL, scratch2));
|
| + __ add(scratch2, scratch1, Operand(0x40000000), SetCC);
|
| + __ b(mi, &stub_call);
|
| + __ SmiTag(right, scratch1);
|
| + break;
|
| + }
|
| + case Token::SHR: {
|
| + __ b(&stub_call);
|
| + __ SmiUntag(scratch1, left);
|
| + __ GetLeastBitsFromSmi(scratch2, right, 5);
|
| + __ mov(scratch1, Operand(scratch1, LSR, scratch2));
|
| + __ tst(scratch1, Operand(0xc0000000));
|
| + __ b(ne, &stub_call);
|
| + __ SmiTag(right, scratch1);
|
| + break;
|
| + }
|
| + case Token::ADD:
|
| + __ add(scratch1, left, Operand(right), SetCC);
|
| + __ b(vs, &stub_call);
|
| + __ mov(right, scratch1);
|
| + break;
|
| + case Token::SUB:
|
| + __ sub(scratch1, left, Operand(right), SetCC);
|
| + __ b(vs, &stub_call);
|
| + __ mov(right, scratch1);
|
| + break;
|
| + case Token::MUL: {
|
| + __ SmiUntag(ip, right);
|
| + __ smull(scratch1, scratch2, left, ip);
|
| + __ mov(ip, Operand(scratch1, ASR, 31));
|
| + __ cmp(ip, Operand(scratch2));
|
| + __ b(ne, &stub_call);
|
| + __ tst(scratch1, Operand(scratch1));
|
| + __ mov(right, Operand(scratch1), LeaveCC, ne);
|
| + __ b(ne, &done);
|
| + __ add(scratch2, right, Operand(left), SetCC);
|
| + __ mov(right, Operand(Smi::FromInt(0)), LeaveCC, pl);
|
| + __ b(mi, &stub_call);
|
| + break;
|
| + }
|
| + case Token::BIT_OR:
|
| + __ orr(right, left, Operand(right));
|
| + break;
|
| + case Token::BIT_AND:
|
| + __ and_(right, left, Operand(right));
|
| + break;
|
| + case Token::BIT_XOR:
|
| + __ eor(right, left, Operand(right));
|
| + break;
|
| + default:
|
| + UNREACHABLE();
|
| + }
|
| +
|
| + __ bind(&done);
|
| + context()->Plug(r0);
|
| }
|
|
|
|
|
| @@ -1661,7 +1840,9 @@
|
| __ mov(r1, r0);
|
| __ pop(r0); // Restore value.
|
| __ mov(r2, Operand(prop->key()->AsLiteral()->handle()));
|
| - Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize));
|
| + Handle<Code> ic(Builtins::builtin(
|
| + is_strict_mode() ? Builtins::StoreIC_Initialize_Strict
|
| + : Builtins::StoreIC_Initialize));
|
| EmitCallIC(ic, RelocInfo::CODE_TARGET);
|
| break;
|
| }
|
| @@ -1682,7 +1863,9 @@
|
| __ pop(r2);
|
| }
|
| __ pop(r0); // Restore value.
|
| - Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Initialize));
|
| + Handle<Code> ic(Builtins::builtin(
|
| + is_strict_mode() ? Builtins::KeyedStoreIC_Initialize_Strict
|
| + : Builtins::KeyedStoreIC_Initialize));
|
| EmitCallIC(ic, RelocInfo::CODE_TARGET);
|
| break;
|
| }
|
| @@ -1706,39 +1889,65 @@
|
| // r2, and the global object in r1.
|
| __ mov(r2, Operand(var->name()));
|
| __ ldr(r1, GlobalObjectOperand());
|
| - Handle<Code> ic(Builtins::builtin(is_strict()
|
| - ? Builtins::StoreIC_Initialize_Strict
|
| - : Builtins::StoreIC_Initialize));
|
| + Handle<Code> ic(Builtins::builtin(
|
| + is_strict_mode() ? Builtins::StoreIC_Initialize_Strict
|
| + : Builtins::StoreIC_Initialize));
|
| EmitCallIC(ic, RelocInfo::CODE_TARGET_CONTEXT);
|
|
|
| - } else if (var->mode() != Variable::CONST || op == Token::INIT_CONST) {
|
| - // Perform the assignment for non-const variables and for initialization
|
| - // of const variables. Const assignments are simply skipped.
|
| - Label done;
|
| + } else if (op == Token::INIT_CONST) {
|
| + // Like var declarations, const declarations are hoisted to function
|
| + // scope. However, unlike var initializers, const initializers are able
|
| + // to drill a hole to that function context, even from inside a 'with'
|
| + // context. We thus bypass the normal static scope lookup.
|
| Slot* slot = var->AsSlot();
|
| + Label skip;
|
| switch (slot->type()) {
|
| case Slot::PARAMETER:
|
| + // No const parameters.
|
| + UNREACHABLE();
|
| + break;
|
| case Slot::LOCAL:
|
| - if (op == Token::INIT_CONST) {
|
| - // Detect const reinitialization by checking for the hole value.
|
| - __ ldr(r1, MemOperand(fp, SlotOffset(slot)));
|
| - __ LoadRoot(ip, Heap::kTheHoleValueRootIndex);
|
| - __ cmp(r1, ip);
|
| - __ b(ne, &done);
|
| - }
|
| + // Detect const reinitialization by checking for the hole value.
|
| + __ ldr(r1, MemOperand(fp, SlotOffset(slot)));
|
| + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex);
|
| + __ cmp(r1, ip);
|
| + __ b(ne, &skip);
|
| + __ str(result_register(), MemOperand(fp, SlotOffset(slot)));
|
| + break;
|
| + case Slot::CONTEXT: {
|
| + __ ldr(r1, ContextOperand(cp, Context::FCONTEXT_INDEX));
|
| + __ ldr(r2, ContextOperand(r1, slot->index()));
|
| + __ LoadRoot(ip, Heap::kTheHoleValueRootIndex);
|
| + __ cmp(r2, ip);
|
| + __ b(ne, &skip);
|
| + __ str(r0, ContextOperand(r1, slot->index()));
|
| + int offset = Context::SlotOffset(slot->index());
|
| + __ mov(r3, r0); // Preserve the stored value in r0.
|
| + __ RecordWrite(r1, Operand(offset), r3, r2);
|
| + break;
|
| + }
|
| + case Slot::LOOKUP:
|
| + __ push(r0);
|
| + __ mov(r0, Operand(slot->var()->name()));
|
| + __ Push(cp, r0); // Context and name.
|
| + __ CallRuntime(Runtime::kInitializeConstContextSlot, 3);
|
| + break;
|
| + }
|
| + __ bind(&skip);
|
| +
|
| + } else if (var->mode() != Variable::CONST) {
|
| + // Perform the assignment for non-const variables. Const assignments
|
| + // are simply skipped.
|
| + Slot* slot = var->AsSlot();
|
| + switch (slot->type()) {
|
| + case Slot::PARAMETER:
|
| + case Slot::LOCAL:
|
| // Perform the assignment.
|
| __ str(result_register(), MemOperand(fp, SlotOffset(slot)));
|
| break;
|
|
|
| case Slot::CONTEXT: {
|
| MemOperand target = EmitSlotSearch(slot, r1);
|
| - if (op == Token::INIT_CONST) {
|
| - // Detect const reinitialization by checking for the hole value.
|
| - __ ldr(r2, target);
|
| - __ LoadRoot(ip, Heap::kTheHoleValueRootIndex);
|
| - __ cmp(r2, ip);
|
| - __ b(ne, &done);
|
| - }
|
| // Perform the assignment and issue the write barrier.
|
| __ str(result_register(), target);
|
| #ifdef ENABLE_CARDMARKING_WRITE_BARRIER
|
| @@ -1751,20 +1960,14 @@
|
| }
|
|
|
| case Slot::LOOKUP:
|
| - // Call the runtime for the assignment. The runtime will ignore
|
| - // const reinitialization.
|
| + // Call the runtime for the assignment.
|
| __ push(r0); // Value.
|
| - __ mov(r0, Operand(slot->var()->name()));
|
| - __ Push(cp, r0); // Context and name.
|
| - if (op == Token::INIT_CONST) {
|
| - // The runtime will ignore const redeclaration.
|
| - __ CallRuntime(Runtime::kInitializeConstContextSlot, 3);
|
| - } else {
|
| - __ CallRuntime(Runtime::kStoreContextSlot, 3);
|
| - }
|
| + __ mov(r1, Operand(slot->var()->name()));
|
| + __ mov(r0, Operand(Smi::FromInt(strict_mode_flag())));
|
| + __ Push(cp, r1, r0); // Context, name, strict mode.
|
| + __ CallRuntime(Runtime::kStoreContextSlot, 4);
|
| break;
|
| }
|
| - __ bind(&done);
|
| }
|
| }
|
|
|
| @@ -1797,7 +2000,9 @@
|
| __ pop(r1);
|
| }
|
|
|
| - Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize));
|
| + Handle<Code> ic(Builtins::builtin(
|
| + is_strict_mode() ? Builtins::StoreIC_Initialize_Strict
|
| + : Builtins::StoreIC_Initialize));
|
| EmitCallIC(ic, RelocInfo::CODE_TARGET);
|
|
|
| // If the assignment ends an initialization block, revert to fast case.
|
| @@ -1841,7 +2046,9 @@
|
| __ pop(r2);
|
| }
|
|
|
| - Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Initialize));
|
| + Handle<Code> ic(Builtins::builtin(
|
| + is_strict_mode() ? Builtins::KeyedStoreIC_Initialize_Strict
|
| + : Builtins::KeyedStoreIC_Initialize));
|
| EmitCallIC(ic, RelocInfo::CODE_TARGET);
|
|
|
| // If the assignment ends an initialization block, revert to fast case.
|
| @@ -1956,6 +2163,29 @@
|
| }
|
|
|
|
|
| +void FullCodeGenerator::EmitResolvePossiblyDirectEval(ResolveEvalFlag flag,
|
| + int arg_count) {
|
| + // Push copy of the first argument or undefined if it doesn't exist.
|
| + if (arg_count > 0) {
|
| + __ ldr(r1, MemOperand(sp, arg_count * kPointerSize));
|
| + } else {
|
| + __ LoadRoot(r1, Heap::kUndefinedValueRootIndex);
|
| + }
|
| + __ push(r1);
|
| +
|
| + // Push the receiver of the enclosing function and do runtime call.
|
| + __ ldr(r1, MemOperand(fp, (2 + scope()->num_parameters()) * kPointerSize));
|
| + __ push(r1);
|
| + // Push the strict mode flag.
|
| + __ mov(r1, Operand(Smi::FromInt(strict_mode_flag())));
|
| + __ push(r1);
|
| +
|
| + __ CallRuntime(flag == SKIP_CONTEXT_LOOKUP
|
| + ? Runtime::kResolvePossiblyDirectEvalNoLookup
|
| + : Runtime::kResolvePossiblyDirectEval, 4);
|
| +}
|
| +
|
| +
|
| void FullCodeGenerator::VisitCall(Call* expr) {
|
| #ifdef DEBUG
|
| // We want to verify that RecordJSReturnSite gets called on all paths
|
| @@ -1985,27 +2215,32 @@
|
| VisitForStackValue(args->at(i));
|
| }
|
|
|
| - // Push copy of the function - found below the arguments.
|
| + // If we know that eval can only be shadowed by eval-introduced
|
| + // variables we attempt to load the global eval function directly
|
| + // in generated code. If we succeed, there is no need to perform a
|
| + // context lookup in the runtime system.
|
| + Label done;
|
| + if (var->AsSlot() != NULL && var->mode() == Variable::DYNAMIC_GLOBAL) {
|
| + Label slow;
|
| + EmitLoadGlobalSlotCheckExtensions(var->AsSlot(),
|
| + NOT_INSIDE_TYPEOF,
|
| + &slow);
|
| + // Push the function and resolve eval.
|
| + __ push(r0);
|
| + EmitResolvePossiblyDirectEval(SKIP_CONTEXT_LOOKUP, arg_count);
|
| + __ jmp(&done);
|
| + __ bind(&slow);
|
| + }
|
| +
|
| + // Push copy of the function (found below the arguments) and
|
| + // resolve eval.
|
| __ ldr(r1, MemOperand(sp, (arg_count + 1) * kPointerSize));
|
| __ push(r1);
|
| -
|
| - // Push copy of the first argument or undefined if it doesn't exist.
|
| - if (arg_count > 0) {
|
| - __ ldr(r1, MemOperand(sp, arg_count * kPointerSize));
|
| - __ push(r1);
|
| - } else {
|
| - __ push(r2);
|
| + EmitResolvePossiblyDirectEval(PERFORM_CONTEXT_LOOKUP, arg_count);
|
| + if (done.is_linked()) {
|
| + __ bind(&done);
|
| }
|
|
|
| - // Push the receiver of the enclosing function and do runtime call.
|
| - __ ldr(r1,
|
| - MemOperand(fp, (2 + scope()->num_parameters()) * kPointerSize));
|
| - __ push(r1);
|
| - // Push the strict mode flag.
|
| - __ mov(r1, Operand(Smi::FromInt(strict_mode_flag())));
|
| - __ push(r1);
|
| - __ CallRuntime(Runtime::kResolvePossiblyDirectEval, 4);
|
| -
|
| // The runtime call returns a pair of values in r0 (function) and
|
| // r1 (receiver). Touch up the stack with the right values.
|
| __ str(r0, MemOperand(sp, (arg_count + 1) * kPointerSize));
|
| @@ -2637,7 +2872,8 @@
|
| ASSERT(args->length() == 2);
|
| VisitForStackValue(args->at(0));
|
| VisitForStackValue(args->at(1));
|
| - __ CallRuntime(Runtime::kMath_pow, 2);
|
| + MathPowStub stub;
|
| + __ CallStub(&stub);
|
| context()->Plug(r0);
|
| }
|
|
|
| @@ -2821,37 +3057,43 @@
|
|
|
|
|
| void FullCodeGenerator::EmitMathSin(ZoneList<Expression*>* args) {
|
| - // Load the argument on the stack and call the runtime.
|
| + // Load the argument on the stack and call the stub.
|
| + TranscendentalCacheStub stub(TranscendentalCache::SIN,
|
| + TranscendentalCacheStub::TAGGED);
|
| ASSERT(args->length() == 1);
|
| VisitForStackValue(args->at(0));
|
| - __ CallRuntime(Runtime::kMath_sin, 1);
|
| + __ CallStub(&stub);
|
| context()->Plug(r0);
|
| }
|
|
|
|
|
| void FullCodeGenerator::EmitMathCos(ZoneList<Expression*>* args) {
|
| - // Load the argument on the stack and call the runtime.
|
| + // Load the argument on the stack and call the stub.
|
| + TranscendentalCacheStub stub(TranscendentalCache::COS,
|
| + TranscendentalCacheStub::TAGGED);
|
| ASSERT(args->length() == 1);
|
| VisitForStackValue(args->at(0));
|
| - __ CallRuntime(Runtime::kMath_cos, 1);
|
| + __ CallStub(&stub);
|
| context()->Plug(r0);
|
| }
|
|
|
|
|
| -void FullCodeGenerator::EmitMathSqrt(ZoneList<Expression*>* args) {
|
| - // Load the argument on the stack and call the runtime function.
|
| +void FullCodeGenerator::EmitMathLog(ZoneList<Expression*>* args) {
|
| + // Load the argument on the stack and call the stub.
|
| + TranscendentalCacheStub stub(TranscendentalCache::LOG,
|
| + TranscendentalCacheStub::TAGGED);
|
| ASSERT(args->length() == 1);
|
| VisitForStackValue(args->at(0));
|
| - __ CallRuntime(Runtime::kMath_sqrt, 1);
|
| + __ CallStub(&stub);
|
| context()->Plug(r0);
|
| }
|
|
|
|
|
| -void FullCodeGenerator::EmitMathLog(ZoneList<Expression*>* args) {
|
| +void FullCodeGenerator::EmitMathSqrt(ZoneList<Expression*>* args) {
|
| // Load the argument on the stack and call the runtime function.
|
| ASSERT(args->length() == 1);
|
| VisitForStackValue(args->at(0));
|
| - __ CallRuntime(Runtime::kMath_log, 1);
|
| + __ CallRuntime(Runtime::kMath_sqrt, 1);
|
| context()->Plug(r0);
|
| }
|
|
|
| @@ -2891,7 +3133,79 @@
|
| VisitForStackValue(args->at(0));
|
| VisitForStackValue(args->at(1));
|
| VisitForStackValue(args->at(2));
|
| + Label done;
|
| + Label slow_case;
|
| + Register object = r0;
|
| + Register index1 = r1;
|
| + Register index2 = r2;
|
| + Register elements = r3;
|
| + Register scratch1 = r4;
|
| + Register scratch2 = r5;
|
| +
|
| + __ ldr(object, MemOperand(sp, 2 * kPointerSize));
|
| + // Fetch the map and check if array is in fast case.
|
| + // Check that object doesn't require security checks and
|
| + // has no indexed interceptor.
|
| + __ CompareObjectType(object, scratch1, scratch2, JS_ARRAY_TYPE);
|
| + __ b(ne, &slow_case);
|
| + // Map is now in scratch1.
|
| +
|
| + __ ldrb(scratch2, FieldMemOperand(scratch1, Map::kBitFieldOffset));
|
| + __ tst(scratch2, Operand(KeyedLoadIC::kSlowCaseBitFieldMask));
|
| + __ b(ne, &slow_case);
|
| +
|
| + // Check the object's elements are in fast case and writable.
|
| + __ ldr(elements, FieldMemOperand(object, JSObject::kElementsOffset));
|
| + __ ldr(scratch1, FieldMemOperand(elements, HeapObject::kMapOffset));
|
| + __ LoadRoot(ip, Heap::kFixedArrayMapRootIndex);
|
| + __ cmp(scratch1, ip);
|
| + __ b(ne, &slow_case);
|
| +
|
| + // Check that both indices are smis.
|
| + __ ldr(index1, MemOperand(sp, 1 * kPointerSize));
|
| + __ ldr(index2, MemOperand(sp, 0));
|
| + __ JumpIfNotBothSmi(index1, index2, &slow_case);
|
| +
|
| + // Check that both indices are valid.
|
| + __ ldr(scratch1, FieldMemOperand(object, JSArray::kLengthOffset));
|
| + __ cmp(scratch1, index1);
|
| + __ cmp(scratch1, index2, hi);
|
| + __ b(ls, &slow_case);
|
| +
|
| + // Bring the address of the elements into index1 and index2.
|
| + __ add(scratch1, elements, Operand(FixedArray::kHeaderSize - kHeapObjectTag));
|
| + __ add(index1,
|
| + scratch1,
|
| + Operand(index1, LSL, kPointerSizeLog2 - kSmiTagSize));
|
| + __ add(index2,
|
| + scratch1,
|
| + Operand(index2, LSL, kPointerSizeLog2 - kSmiTagSize));
|
| +
|
| + // Swap elements.
|
| + __ ldr(scratch1, MemOperand(index1, 0));
|
| + __ ldr(scratch2, MemOperand(index2, 0));
|
| + __ str(scratch1, MemOperand(index2, 0));
|
| + __ str(scratch2, MemOperand(index1, 0));
|
| +
|
| + Label new_space;
|
| + __ InNewSpace(elements, scratch1, eq, &new_space);
|
| + // Possible optimization: do a check that both values are Smis
|
| + // (or them and test against Smi mask.)
|
| +
|
| + __ mov(scratch1, elements);
|
| + __ RecordWriteHelper(elements, index1, scratch2);
|
| + __ RecordWriteHelper(scratch1, index2, scratch2); // scratch1 holds elements.
|
| +
|
| + __ bind(&new_space);
|
| + // We are done. Drop elements from the stack, and return undefined.
|
| + __ Drop(3);
|
| + __ LoadRoot(r0, Heap::kUndefinedValueRootIndex);
|
| + __ jmp(&done);
|
| +
|
| + __ bind(&slow_case);
|
| __ CallRuntime(Runtime::kSwapElements, 3);
|
| +
|
| + __ bind(&done);
|
| context()->Plug(r0);
|
| }
|
|
|
| @@ -3010,16 +3324,248 @@
|
| void FullCodeGenerator::EmitGetCachedArrayIndex(ZoneList<Expression*>* args) {
|
| ASSERT(args->length() == 1);
|
| VisitForAccumulatorValue(args->at(0));
|
| +
|
| + if (FLAG_debug_code) {
|
| + __ AbortIfNotString(r0);
|
| + }
|
| +
|
| __ ldr(r0, FieldMemOperand(r0, String::kHashFieldOffset));
|
| __ IndexFromHash(r0, r0);
|
| +
|
| context()->Plug(r0);
|
| }
|
|
|
|
|
| void FullCodeGenerator::EmitFastAsciiArrayJoin(ZoneList<Expression*>* args) {
|
| + Label bailout, done, one_char_separator, long_separator,
|
| + non_trivial_array, not_size_one_array, loop,
|
| + empty_separator_loop, one_char_separator_loop,
|
| + one_char_separator_loop_entry, long_separator_loop;
|
| +
|
| + ASSERT(args->length() == 2);
|
| + VisitForStackValue(args->at(1));
|
| + VisitForAccumulatorValue(args->at(0));
|
| +
|
| + // All aliases of the same register have disjoint lifetimes.
|
| + Register array = r0;
|
| + Register elements = no_reg; // Will be r0.
|
| + Register result = no_reg; // Will be r0.
|
| + Register separator = r1;
|
| + Register array_length = r2;
|
| + Register result_pos = no_reg; // Will be r2
|
| + Register string_length = r3;
|
| + Register string = r4;
|
| + Register element = r5;
|
| + Register elements_end = r6;
|
| + Register scratch1 = r7;
|
| + Register scratch2 = r9;
|
| +
|
| + // Separator operand is on the stack.
|
| + __ pop(separator);
|
| +
|
| + // Check that the array is a JSArray.
|
| + __ JumpIfSmi(array, &bailout);
|
| + __ CompareObjectType(array, scratch1, scratch2, JS_ARRAY_TYPE);
|
| + __ b(ne, &bailout);
|
| +
|
| + // Check that the array has fast elements.
|
| + __ ldrb(scratch2, FieldMemOperand(scratch1, Map::kBitField2Offset));
|
| + __ tst(scratch2, Operand(1 << Map::kHasFastElements));
|
| + __ b(eq, &bailout);
|
| +
|
| + // If the array has length zero, return the empty string.
|
| + __ ldr(array_length, FieldMemOperand(array, JSArray::kLengthOffset));
|
| + __ SmiUntag(array_length, SetCC);
|
| + __ b(ne, &non_trivial_array);
|
| + __ LoadRoot(r0, Heap::kEmptyStringRootIndex);
|
| + __ b(&done);
|
| +
|
| + __ bind(&non_trivial_array);
|
| +
|
| + // Get the FixedArray containing array's elements.
|
| + elements = array;
|
| + __ ldr(elements, FieldMemOperand(array, JSArray::kElementsOffset));
|
| + array = no_reg; // End of array's live range.
|
| +
|
| + // Check that all array elements are sequential ASCII strings, and
|
| + // accumulate the sum of their lengths, as a smi-encoded value.
|
| + __ mov(string_length, Operand(0));
|
| + __ add(element,
|
| + elements, Operand(FixedArray::kHeaderSize - kHeapObjectTag));
|
| + __ add(elements_end, element, Operand(array_length, LSL, kPointerSizeLog2));
|
| + // Loop condition: while (element < elements_end).
|
| + // Live values in registers:
|
| + // elements: Fixed array of strings.
|
| + // array_length: Length of the fixed array of strings (not smi)
|
| + // separator: Separator string
|
| + // string_length: Accumulated sum of string lengths (smi).
|
| + // element: Current array element.
|
| + // elements_end: Array end.
|
| + if (FLAG_debug_code) {
|
| + __ cmp(array_length, Operand(0));
|
| + __ Assert(gt, "No empty arrays here in EmitFastAsciiArrayJoin");
|
| + }
|
| + __ bind(&loop);
|
| + __ ldr(string, MemOperand(element, kPointerSize, PostIndex));
|
| + __ JumpIfSmi(string, &bailout);
|
| + __ ldr(scratch1, FieldMemOperand(string, HeapObject::kMapOffset));
|
| + __ ldrb(scratch1, FieldMemOperand(scratch1, Map::kInstanceTypeOffset));
|
| + __ JumpIfInstanceTypeIsNotSequentialAscii(scratch1, scratch2, &bailout);
|
| + __ ldr(scratch1, FieldMemOperand(string, SeqAsciiString::kLengthOffset));
|
| + __ add(string_length, string_length, Operand(scratch1));
|
| + __ b(vs, &bailout);
|
| + __ cmp(element, elements_end);
|
| + __ b(lt, &loop);
|
| +
|
| + // If array_length is 1, return elements[0], a string.
|
| + __ cmp(array_length, Operand(1));
|
| + __ b(ne, ¬_size_one_array);
|
| + __ ldr(r0, FieldMemOperand(elements, FixedArray::kHeaderSize));
|
| + __ b(&done);
|
| +
|
| + __ bind(¬_size_one_array);
|
| +
|
| + // Live values in registers:
|
| + // separator: Separator string
|
| + // array_length: Length of the array.
|
| + // string_length: Sum of string lengths (smi).
|
| + // elements: FixedArray of strings.
|
| +
|
| + // Check that the separator is a flat ASCII string.
|
| + __ JumpIfSmi(separator, &bailout);
|
| + __ ldr(scratch1, FieldMemOperand(separator, HeapObject::kMapOffset));
|
| + __ ldrb(scratch1, FieldMemOperand(scratch1, Map::kInstanceTypeOffset));
|
| + __ JumpIfInstanceTypeIsNotSequentialAscii(scratch1, scratch2, &bailout);
|
| +
|
| + // Add (separator length times array_length) - separator length to the
|
| + // string_length to get the length of the result string. array_length is not
|
| + // smi but the other values are, so the result is a smi
|
| + __ ldr(scratch1, FieldMemOperand(separator, SeqAsciiString::kLengthOffset));
|
| + __ sub(string_length, string_length, Operand(scratch1));
|
| + __ smull(scratch2, ip, array_length, scratch1);
|
| + // Check for smi overflow. No overflow if higher 33 bits of 64-bit result are
|
| + // zero.
|
| + __ cmp(ip, Operand(0));
|
| + __ b(ne, &bailout);
|
| + __ tst(scratch2, Operand(0x80000000));
|
| + __ b(ne, &bailout);
|
| + __ add(string_length, string_length, Operand(scratch2));
|
| + __ b(vs, &bailout);
|
| + __ SmiUntag(string_length);
|
| +
|
| + // Get first element in the array to free up the elements register to be used
|
| + // for the result.
|
| + __ add(element,
|
| + elements, Operand(FixedArray::kHeaderSize - kHeapObjectTag));
|
| + result = elements; // End of live range for elements.
|
| + elements = no_reg;
|
| + // Live values in registers:
|
| + // element: First array element
|
| + // separator: Separator string
|
| + // string_length: Length of result string (not smi)
|
| + // array_length: Length of the array.
|
| + __ AllocateAsciiString(result,
|
| + string_length,
|
| + scratch1,
|
| + scratch2,
|
| + elements_end,
|
| + &bailout);
|
| + // Prepare for looping. Set up elements_end to end of the array. Set
|
| + // result_pos to the position of the result where to write the first
|
| + // character.
|
| + __ add(elements_end, element, Operand(array_length, LSL, kPointerSizeLog2));
|
| + result_pos = array_length; // End of live range for array_length.
|
| + array_length = no_reg;
|
| + __ add(result_pos,
|
| + result,
|
| + Operand(SeqAsciiString::kHeaderSize - kHeapObjectTag));
|
| +
|
| + // Check the length of the separator.
|
| + __ ldr(scratch1, FieldMemOperand(separator, SeqAsciiString::kLengthOffset));
|
| + __ cmp(scratch1, Operand(Smi::FromInt(1)));
|
| + __ b(eq, &one_char_separator);
|
| + __ b(gt, &long_separator);
|
| +
|
| + // Empty separator case
|
| + __ bind(&empty_separator_loop);
|
| + // Live values in registers:
|
| + // result_pos: the position to which we are currently copying characters.
|
| + // element: Current array element.
|
| + // elements_end: Array end.
|
| +
|
| + // Copy next array element to the result.
|
| + __ ldr(string, MemOperand(element, kPointerSize, PostIndex));
|
| + __ ldr(string_length, FieldMemOperand(string, String::kLengthOffset));
|
| + __ SmiUntag(string_length);
|
| + __ add(string, string, Operand(SeqAsciiString::kHeaderSize - kHeapObjectTag));
|
| + __ CopyBytes(string, result_pos, string_length, scratch1);
|
| + __ cmp(element, elements_end);
|
| + __ b(lt, &empty_separator_loop); // End while (element < elements_end).
|
| + ASSERT(result.is(r0));
|
| + __ b(&done);
|
| +
|
| + // One-character separator case
|
| + __ bind(&one_char_separator);
|
| + // Replace separator with its ascii character value.
|
| + __ ldrb(separator, FieldMemOperand(separator, SeqAsciiString::kHeaderSize));
|
| + // Jump into the loop after the code that copies the separator, so the first
|
| + // element is not preceded by a separator
|
| + __ jmp(&one_char_separator_loop_entry);
|
| +
|
| + __ bind(&one_char_separator_loop);
|
| + // Live values in registers:
|
| + // result_pos: the position to which we are currently copying characters.
|
| + // element: Current array element.
|
| + // elements_end: Array end.
|
| + // separator: Single separator ascii char (in lower byte).
|
| +
|
| + // Copy the separator character to the result.
|
| + __ strb(separator, MemOperand(result_pos, 1, PostIndex));
|
| +
|
| + // Copy next array element to the result.
|
| + __ bind(&one_char_separator_loop_entry);
|
| + __ ldr(string, MemOperand(element, kPointerSize, PostIndex));
|
| + __ ldr(string_length, FieldMemOperand(string, String::kLengthOffset));
|
| + __ SmiUntag(string_length);
|
| + __ add(string, string, Operand(SeqAsciiString::kHeaderSize - kHeapObjectTag));
|
| + __ CopyBytes(string, result_pos, string_length, scratch1);
|
| + __ cmp(element, elements_end);
|
| + __ b(lt, &one_char_separator_loop); // End while (element < elements_end).
|
| + ASSERT(result.is(r0));
|
| + __ b(&done);
|
| +
|
| + // Long separator case (separator is more than one character). Entry is at the
|
| + // label long_separator below.
|
| + __ bind(&long_separator_loop);
|
| + // Live values in registers:
|
| + // result_pos: the position to which we are currently copying characters.
|
| + // element: Current array element.
|
| + // elements_end: Array end.
|
| + // separator: Separator string.
|
| +
|
| + // Copy the separator to the result.
|
| + __ ldr(string_length, FieldMemOperand(separator, String::kLengthOffset));
|
| + __ SmiUntag(string_length);
|
| + __ add(string,
|
| + separator,
|
| + Operand(SeqAsciiString::kHeaderSize - kHeapObjectTag));
|
| + __ CopyBytes(string, result_pos, string_length, scratch1);
|
| +
|
| + __ bind(&long_separator);
|
| + __ ldr(string, MemOperand(element, kPointerSize, PostIndex));
|
| + __ ldr(string_length, FieldMemOperand(string, String::kLengthOffset));
|
| + __ SmiUntag(string_length);
|
| + __ add(string, string, Operand(SeqAsciiString::kHeaderSize - kHeapObjectTag));
|
| + __ CopyBytes(string, result_pos, string_length, scratch1);
|
| + __ cmp(element, elements_end);
|
| + __ b(lt, &long_separator_loop); // End while (element < elements_end).
|
| + ASSERT(result.is(r0));
|
| + __ b(&done);
|
| +
|
| + __ bind(&bailout);
|
| __ LoadRoot(r0, Heap::kUndefinedValueRootIndex);
|
| + __ bind(&done);
|
| context()->Plug(r0);
|
| - return;
|
| }
|
|
|
|
|
| @@ -3084,8 +3630,8 @@
|
| }
|
| } else if (var != NULL) {
|
| // Delete of an unqualified identifier is disallowed in strict mode
|
| - // so this code can only be reached in non-strict mode.
|
| - ASSERT(strict_mode_flag() == kNonStrictMode);
|
| + // but "delete this" is.
|
| + ASSERT(strict_mode_flag() == kNonStrictMode || var->is_this());
|
| if (var->is_global()) {
|
| __ ldr(r2, GlobalObjectOperand());
|
| __ mov(r1, Operand(var->name()));
|
| @@ -3125,17 +3671,23 @@
|
|
|
| case Token::NOT: {
|
| Comment cmnt(masm_, "[ UnaryOperation (NOT)");
|
| - Label materialize_true, materialize_false;
|
| - Label* if_true = NULL;
|
| - Label* if_false = NULL;
|
| - Label* fall_through = NULL;
|
| + if (context()->IsEffect()) {
|
| + // Unary NOT has no side effects so it's only necessary to visit the
|
| + // subexpression. Match the optimizing compiler by not branching.
|
| + VisitForEffect(expr->expression());
|
| + } else {
|
| + Label materialize_true, materialize_false;
|
| + Label* if_true = NULL;
|
| + Label* if_false = NULL;
|
| + Label* fall_through = NULL;
|
|
|
| - // Notice that the labels are swapped.
|
| - context()->PrepareTest(&materialize_true, &materialize_false,
|
| - &if_false, &if_true, &fall_through);
|
| - if (context()->IsTest()) ForwardBailoutToChild(expr);
|
| - VisitForControl(expr->expression(), if_true, if_false, fall_through);
|
| - context()->Plug(if_false, if_true); // Labels swapped.
|
| + // Notice that the labels are swapped.
|
| + context()->PrepareTest(&materialize_true, &materialize_false,
|
| + &if_false, &if_true, &fall_through);
|
| + if (context()->IsTest()) ForwardBailoutToChild(expr);
|
| + VisitForControl(expr->expression(), if_true, if_false, fall_through);
|
| + context()->Plug(if_false, if_true); // Labels swapped.
|
| + }
|
| break;
|
| }
|
|
|
| @@ -3167,9 +3719,7 @@
|
| bool can_overwrite = expr->expression()->ResultOverwriteAllowed();
|
| UnaryOverwriteMode overwrite =
|
| can_overwrite ? UNARY_OVERWRITE : UNARY_NO_OVERWRITE;
|
| - GenericUnaryOpStub stub(Token::SUB,
|
| - overwrite,
|
| - NO_UNARY_FLAGS);
|
| + GenericUnaryOpStub stub(Token::SUB, overwrite, NO_UNARY_FLAGS);
|
| // GenericUnaryOpStub expects the argument to be in the
|
| // accumulator register r0.
|
| VisitForAccumulatorValue(expr->expression());
|
| @@ -3302,13 +3852,16 @@
|
|
|
| // Inline smi case if we are in a loop.
|
| Label stub_call, done;
|
| + JumpPatchSite patch_site(masm_);
|
| +
|
| int count_value = expr->op() == Token::INC ? 1 : -1;
|
| if (ShouldInlineSmiCase(expr->op())) {
|
| __ 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.
|
| - __ JumpIfSmi(r0, &done);
|
| + patch_site.EmitJumpIfSmi(r0, &done);
|
| +
|
| __ bind(&stub_call);
|
| // Call stub. Undo operation first.
|
| __ sub(r0, r0, Operand(Smi::FromInt(count_value)));
|
| @@ -3318,8 +3871,8 @@
|
| // Record position before stub call.
|
| SetSourcePosition(expr->position());
|
|
|
| - GenericBinaryOpStub stub(Token::ADD, NO_OVERWRITE, r1, r0);
|
| - __ CallStub(&stub);
|
| + TypeRecordingBinaryOpStub stub(Token::ADD, NO_OVERWRITE);
|
| + EmitCallIC(stub.GetCode(), &patch_site);
|
| __ bind(&done);
|
|
|
| // Store the value returned in r0.
|
| @@ -3347,7 +3900,9 @@
|
| case NAMED_PROPERTY: {
|
| __ mov(r2, Operand(prop->key()->AsLiteral()->handle()));
|
| __ pop(r1);
|
| - Handle<Code> ic(Builtins::builtin(Builtins::StoreIC_Initialize));
|
| + Handle<Code> ic(Builtins::builtin(
|
| + is_strict_mode() ? Builtins::StoreIC_Initialize_Strict
|
| + : Builtins::StoreIC_Initialize));
|
| EmitCallIC(ic, RelocInfo::CODE_TARGET);
|
| PrepareForBailoutForId(expr->AssignmentId(), TOS_REG);
|
| if (expr->is_postfix()) {
|
| @@ -3362,7 +3917,9 @@
|
| case KEYED_PROPERTY: {
|
| __ pop(r1); // Key.
|
| __ pop(r2); // Receiver.
|
| - Handle<Code> ic(Builtins::builtin(Builtins::KeyedStoreIC_Initialize));
|
| + Handle<Code> ic(Builtins::builtin(
|
| + is_strict_mode() ? Builtins::KeyedStoreIC_Initialize_Strict
|
| + : Builtins::KeyedStoreIC_Initialize));
|
| EmitCallIC(ic, RelocInfo::CODE_TARGET);
|
| PrepareForBailoutForId(expr->AssignmentId(), TOS_REG);
|
| if (expr->is_postfix()) {
|
| @@ -3440,71 +3997,52 @@
|
| PrepareForBailoutBeforeSplit(TOS_REG, true, if_true, if_false);
|
|
|
| if (check->Equals(Heap::number_symbol())) {
|
| - __ tst(r0, Operand(kSmiTagMask));
|
| - __ b(eq, if_true);
|
| + __ JumpIfSmi(r0, if_true);
|
| __ ldr(r0, FieldMemOperand(r0, HeapObject::kMapOffset));
|
| __ LoadRoot(ip, Heap::kHeapNumberMapRootIndex);
|
| __ cmp(r0, ip);
|
| Split(eq, if_true, if_false, fall_through);
|
| } else if (check->Equals(Heap::string_symbol())) {
|
| - __ tst(r0, Operand(kSmiTagMask));
|
| - __ b(eq, if_false);
|
| + __ JumpIfSmi(r0, if_false);
|
| // Check for undetectable objects => false.
|
| - __ ldr(r0, FieldMemOperand(r0, HeapObject::kMapOffset));
|
| + __ CompareObjectType(r0, r0, r1, FIRST_NONSTRING_TYPE);
|
| + __ b(ge, if_false);
|
| __ ldrb(r1, FieldMemOperand(r0, Map::kBitFieldOffset));
|
| - __ and_(r1, r1, Operand(1 << Map::kIsUndetectable));
|
| - __ cmp(r1, Operand(1 << Map::kIsUndetectable));
|
| - __ b(eq, if_false);
|
| - __ ldrb(r1, FieldMemOperand(r0, Map::kInstanceTypeOffset));
|
| - __ cmp(r1, Operand(FIRST_NONSTRING_TYPE));
|
| - Split(lt, if_true, if_false, fall_through);
|
| + __ tst(r1, Operand(1 << Map::kIsUndetectable));
|
| + Split(eq, if_true, if_false, fall_through);
|
| } else if (check->Equals(Heap::boolean_symbol())) {
|
| - __ LoadRoot(ip, Heap::kTrueValueRootIndex);
|
| - __ cmp(r0, ip);
|
| + __ CompareRoot(r0, Heap::kTrueValueRootIndex);
|
| __ b(eq, if_true);
|
| - __ LoadRoot(ip, Heap::kFalseValueRootIndex);
|
| - __ cmp(r0, ip);
|
| + __ CompareRoot(r0, Heap::kFalseValueRootIndex);
|
| Split(eq, if_true, if_false, fall_through);
|
| } else if (check->Equals(Heap::undefined_symbol())) {
|
| - __ LoadRoot(ip, Heap::kUndefinedValueRootIndex);
|
| - __ cmp(r0, ip);
|
| + __ CompareRoot(r0, Heap::kUndefinedValueRootIndex);
|
| __ b(eq, if_true);
|
| - __ tst(r0, Operand(kSmiTagMask));
|
| - __ b(eq, if_false);
|
| + __ JumpIfSmi(r0, if_false);
|
| // Check for undetectable objects => true.
|
| __ ldr(r0, FieldMemOperand(r0, HeapObject::kMapOffset));
|
| __ ldrb(r1, FieldMemOperand(r0, Map::kBitFieldOffset));
|
| - __ and_(r1, r1, Operand(1 << Map::kIsUndetectable));
|
| - __ cmp(r1, Operand(1 << Map::kIsUndetectable));
|
| - Split(eq, if_true, if_false, fall_through);
|
| + __ tst(r1, Operand(1 << Map::kIsUndetectable));
|
| + Split(ne, if_true, if_false, fall_through);
|
| +
|
| } else if (check->Equals(Heap::function_symbol())) {
|
| - __ tst(r0, Operand(kSmiTagMask));
|
| - __ b(eq, if_false);
|
| - __ CompareObjectType(r0, r1, r0, JS_FUNCTION_TYPE);
|
| - __ b(eq, if_true);
|
| - // Regular expressions => 'function' (they are callable).
|
| - __ CompareInstanceType(r1, r0, JS_REGEXP_TYPE);
|
| - Split(eq, if_true, if_false, fall_through);
|
| + __ JumpIfSmi(r0, if_false);
|
| + __ CompareObjectType(r0, r1, r0, FIRST_FUNCTION_CLASS_TYPE);
|
| + Split(ge, if_true, if_false, fall_through);
|
| +
|
| } else if (check->Equals(Heap::object_symbol())) {
|
| - __ tst(r0, Operand(kSmiTagMask));
|
| - __ b(eq, if_false);
|
| - __ LoadRoot(ip, Heap::kNullValueRootIndex);
|
| - __ cmp(r0, ip);
|
| + __ JumpIfSmi(r0, if_false);
|
| + __ CompareRoot(r0, Heap::kNullValueRootIndex);
|
| __ b(eq, if_true);
|
| - // Regular expressions => 'function', not 'object'.
|
| - __ CompareObjectType(r0, r1, r0, JS_REGEXP_TYPE);
|
| - __ b(eq, if_false);
|
| + // Check for JS objects => true.
|
| + __ CompareObjectType(r0, r0, r1, FIRST_JS_OBJECT_TYPE);
|
| + __ b(lo, if_false);
|
| + __ CompareInstanceType(r0, r1, FIRST_FUNCTION_CLASS_TYPE);
|
| + __ b(hs, if_false);
|
| // Check for undetectable objects => false.
|
| - __ ldrb(r0, FieldMemOperand(r1, Map::kBitFieldOffset));
|
| - __ and_(r0, r0, Operand(1 << Map::kIsUndetectable));
|
| - __ cmp(r0, Operand(1 << Map::kIsUndetectable));
|
| - __ b(eq, if_false);
|
| - // Check for JS objects => true.
|
| - __ ldrb(r0, FieldMemOperand(r1, Map::kInstanceTypeOffset));
|
| - __ cmp(r0, Operand(FIRST_JS_OBJECT_TYPE));
|
| - __ b(lt, if_false);
|
| - __ cmp(r0, Operand(LAST_JS_OBJECT_TYPE));
|
| - Split(le, if_true, if_false, fall_through);
|
| + __ ldrb(r1, FieldMemOperand(r0, Map::kBitFieldOffset));
|
| + __ tst(r1, Operand(1 << Map::kIsUndetectable));
|
| + Split(eq, if_true, if_false, fall_through);
|
| } else {
|
| if (if_false != fall_through) __ jmp(if_false);
|
| }
|
| @@ -3676,11 +4214,43 @@
|
| void FullCodeGenerator::EmitCallIC(Handle<Code> ic, RelocInfo::Mode mode) {
|
| ASSERT(mode == RelocInfo::CODE_TARGET ||
|
| mode == RelocInfo::CODE_TARGET_CONTEXT);
|
| + switch (ic->kind()) {
|
| + case Code::LOAD_IC:
|
| + __ IncrementCounter(&Counters::named_load_full, 1, r1, r2);
|
| + break;
|
| + case Code::KEYED_LOAD_IC:
|
| + __ IncrementCounter(&Counters::keyed_load_full, 1, r1, r2);
|
| + break;
|
| + case Code::STORE_IC:
|
| + __ IncrementCounter(&Counters::named_store_full, 1, r1, r2);
|
| + break;
|
| + case Code::KEYED_STORE_IC:
|
| + __ IncrementCounter(&Counters::keyed_store_full, 1, r1, r2);
|
| + default:
|
| + break;
|
| + }
|
| +
|
| __ Call(ic, mode);
|
| }
|
|
|
|
|
| void FullCodeGenerator::EmitCallIC(Handle<Code> ic, JumpPatchSite* patch_site) {
|
| + switch (ic->kind()) {
|
| + case Code::LOAD_IC:
|
| + __ IncrementCounter(&Counters::named_load_full, 1, r1, r2);
|
| + break;
|
| + case Code::KEYED_LOAD_IC:
|
| + __ IncrementCounter(&Counters::keyed_load_full, 1, r1, r2);
|
| + break;
|
| + case Code::STORE_IC:
|
| + __ IncrementCounter(&Counters::named_store_full, 1, r1, r2);
|
| + break;
|
| + case Code::KEYED_STORE_IC:
|
| + __ IncrementCounter(&Counters::keyed_store_full, 1, r1, r2);
|
| + default:
|
| + break;
|
| + }
|
| +
|
| __ Call(ic, RelocInfo::CODE_TARGET);
|
| if (patch_site != NULL && patch_site->is_bound()) {
|
| patch_site->EmitPatchInfo();
|
|
|