Commit 43e28a6f authored by serya@chromium.org's avatar serya@chromium.org

Port prototype-call-stubs for normal objects (http://codereview.chromium.org/2801018).

Review URL: http://codereview.chromium.org/2860049

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@5044 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent abaf8347
......@@ -873,88 +873,6 @@ void MacroAssembler::PopTryHandler() {
}
Register MacroAssembler::CheckMaps(JSObject* object, Register object_reg,
JSObject* holder, Register holder_reg,
Register scratch,
int save_at_depth,
Label* miss) {
// Make sure there's no overlap between scratch and the other
// registers.
ASSERT(!scratch.is(object_reg) && !scratch.is(holder_reg));
// Keep track of the current object in register reg.
Register reg = object_reg;
int depth = 0;
if (save_at_depth == depth) {
str(reg, MemOperand(sp));
}
// Check the maps in the prototype chain.
// Traverse the prototype chain from the object and do map checks.
while (object != holder) {
depth++;
// Only global objects and objects that do not require access
// checks are allowed in stubs.
ASSERT(object->IsJSGlobalProxy() || !object->IsAccessCheckNeeded());
// Get the map of the current object.
ldr(scratch, FieldMemOperand(reg, HeapObject::kMapOffset));
cmp(scratch, Operand(Handle<Map>(object->map())));
// Branch on the result of the map check.
b(ne, miss);
// Check access rights to the global object. This has to happen
// after the map check so that we know that the object is
// actually a global object.
if (object->IsJSGlobalProxy()) {
CheckAccessGlobalProxy(reg, scratch, miss);
// Restore scratch register to be the map of the object. In the
// new space case below, we load the prototype from the map in
// the scratch register.
ldr(scratch, FieldMemOperand(reg, HeapObject::kMapOffset));
}
reg = holder_reg; // from now the object is in holder_reg
JSObject* prototype = JSObject::cast(object->GetPrototype());
if (Heap::InNewSpace(prototype)) {
// The prototype is in new space; we cannot store a reference
// to it in the code. Load it from the map.
ldr(reg, FieldMemOperand(scratch, Map::kPrototypeOffset));
} else {
// The prototype is in old space; load it directly.
mov(reg, Operand(Handle<JSObject>(prototype)));
}
if (save_at_depth == depth) {
str(reg, MemOperand(sp));
}
// Go to the next object in the prototype chain.
object = prototype;
}
// Check the holder map.
ldr(scratch, FieldMemOperand(reg, HeapObject::kMapOffset));
cmp(scratch, Operand(Handle<Map>(object->map())));
b(ne, miss);
// Log the check depth.
LOG(IntEvent("check-maps-depth", depth + 1));
// Perform security check for access to the global object and return
// the holder register.
ASSERT(object == holder);
ASSERT(object->IsJSGlobalProxy() || !object->IsAccessCheckNeeded());
if (object->IsJSGlobalProxy()) {
CheckAccessGlobalProxy(reg, scratch, miss);
}
return reg;
}
void MacroAssembler::CheckAccessGlobalProxy(Register holder_reg,
Register scratch,
Label* miss) {
......
......@@ -316,24 +316,6 @@ class MacroAssembler: public Assembler {
// ---------------------------------------------------------------------------
// Inline caching support
// Generates code that verifies that the maps of objects in the
// prototype chain of object hasn't changed since the code was
// generated and branches to the miss label if any map has. If
// necessary the function also generates code for security check
// in case of global object holders. The scratch and holder
// registers are always clobbered, but the object register is only
// clobbered if it the same as the holder register. The function
// returns a register containing the holder - either object_reg or
// holder_reg.
// The function can optionally (when save_at_depth !=
// kInvalidProtoDepth) save the object at the given depth by moving
// it to [sp].
Register CheckMaps(JSObject* object, Register object_reg,
JSObject* holder, Register holder_reg,
Register scratch,
int save_at_depth,
Label* miss);
// Generate code for checking access rights - used for security checks
// on access to global objects across environments. The holder register
// is left untouched, whereas both scratch registers are clobbered.
......
......@@ -83,6 +83,136 @@ static void ProbeTable(MacroAssembler* masm,
}
// Helper function used to check that the dictionary doesn't contain
// the property. This function may return false negatives, so miss_label
// must always call a backup property check that is complete.
// This function is safe to call if the receiver has fast properties.
// Name must be a symbol and receiver must be a heap object.
static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
Label* miss_label,
Register receiver,
String* name,
Register r0,
Register extra) {
ASSERT(name->IsSymbol());
if (!extra.is(no_reg)) {
__ IncrementCounter(&Counters::negative_lookups, 1, r0, extra);
__ IncrementCounter(&Counters::negative_lookups_miss, 1, r0, extra);
}
Label done;
const int kInterceptorOrAccessCheckNeededMask =
(1 << Map::kHasNamedInterceptor) | (1 << Map::kIsAccessCheckNeeded);
// Bail out if the receiver has a named interceptor or requires access checks.
Register map = extra.is(no_reg) ? r0 : extra;
__ ldr(map, FieldMemOperand(receiver, HeapObject::kMapOffset));
__ ldrb(r0, FieldMemOperand(map, Map::kBitFieldOffset));
__ tst(r0, Operand(kInterceptorOrAccessCheckNeededMask));
__ b(ne, miss_label);
// Check that receiver is a JSObject.
if (extra.is(no_reg)) {
__ ldr(map, FieldMemOperand(receiver, HeapObject::kMapOffset));
}
__ ldrb(r0, FieldMemOperand(map, Map::kInstanceTypeOffset));
__ cmp(r0, Operand(FIRST_JS_OBJECT_TYPE));
__ b(lt, miss_label);
// Load properties array.
Register properties = r0;
__ ldr(properties, FieldMemOperand(receiver, JSObject::kPropertiesOffset));
// Check that the properties array is a dictionary.
if (!extra.is(no_reg)) {
__ ldr(extra, FieldMemOperand(properties, HeapObject::kMapOffset));
Register tmp = properties;
__ LoadRoot(tmp, Heap::kHashTableMapRootIndex);
__ cmp(extra, tmp);
} else {
Register tmp1 = receiver;
Register tmp2 = properties;
__ push(tmp1);
__ ldr(tmp1, FieldMemOperand(properties, HeapObject::kMapOffset));
__ LoadRoot(tmp2, Heap::kHashTableMapRootIndex);
__ cmp(tmp1, tmp2);
__ pop(tmp1);
}
__ b(ne, miss_label);
// Restore the temporarily used register.
__ ldr(properties, FieldMemOperand(receiver, JSObject::kPropertiesOffset));
// Compute the capacity mask.
const int kCapacityOffset =
StringDictionary::kHeaderSize +
StringDictionary::kCapacityIndex * kPointerSize;
// Generate an unrolled loop that performs a few probes before
// giving up.
static const int kProbes = 4;
const int kElementsStartOffset =
StringDictionary::kHeaderSize +
StringDictionary::kElementsStartIndex * kPointerSize;
// If names of slots in range from 1 to kProbes - 1 for the hash value are
// not equal to the name and kProbes-th slot is not used (its name is the
// undefined value), it guarantees the hash table doesn't contain the
// property. It's true even if some slots represent deleted properties
// (their names are the null value).
for (int i = 0; i < kProbes; i++) {
// r0 points to properties hash.
// Compute the masked index: (hash + i + i * i) & mask.
if (extra.is(no_reg)) {
__ push(receiver);
}
Register index = extra.is(no_reg) ? receiver : extra;
// Capacity is smi 2^n.
__ ldr(index, FieldMemOperand(properties, kCapacityOffset));
__ sub(index, index, Operand(1));
__ and_(index, index, Operand(
Smi::FromInt(name->Hash() + StringDictionary::GetProbeOffset(i))));
// Scale the index by multiplying by the entry size.
ASSERT(StringDictionary::kEntrySize == 3);
__ add(index, index, Operand(index, LSL, 1)); // index *= 3.
Register entity_name = extra.is(no_reg) ? properties : extra;
// Having undefined at this place means the name is not contained.
ASSERT_EQ(kSmiTagSize, 1);
Register tmp = extra.is(no_reg) ? receiver : properties;
__ add(tmp, properties, Operand(index, LSL, 1));
__ ldr(entity_name, FieldMemOperand(tmp, kElementsStartOffset));
ASSERT(!tmp.is(entity_name));
__ LoadRoot(tmp, Heap::kUndefinedValueRootIndex);
__ cmp(entity_name, tmp);
if (extra.is(no_reg)) {
// 'receiver' shares a register with 'entity_name'.
__ pop(receiver);
}
if (i != kProbes - 1) {
__ b(eq, &done);
// Stop if found the property.
__ cmp(entity_name, Operand(Handle<String>(name)));
__ b(eq, miss_label);
// Restore the properties.
__ ldr(properties,
FieldMemOperand(receiver, JSObject::kPropertiesOffset));
} else {
// Give up probing if still not found the undefined value.
__ b(ne, miss_label);
}
}
__ bind(&done);
if (!extra.is(no_reg)) {
__ DecrementCounter(&Counters::negative_lookups_miss, 1, r0, extra);
}
}
void StubCache::GenerateProbe(MacroAssembler* masm,
Code::Flags flags,
Register receiver,
......@@ -743,31 +873,128 @@ Register StubCompiler::CheckPrototypes(JSObject* object,
int save_at_depth,
Label* miss,
Register extra) {
// Check that the maps haven't changed.
Register result =
masm()->CheckMaps(object, object_reg, holder, holder_reg, scratch,
save_at_depth, miss);
// Make sure there's no overlap between scratch and the other
// registers.
ASSERT(!scratch.is(object_reg) && !scratch.is(holder_reg));
// Keep track of the current object in register reg.
Register reg = object_reg;
int depth = 0;
if (save_at_depth == depth) {
__ str(reg, MemOperand(sp));
}
// Check the maps in the prototype chain.
// Traverse the prototype chain from the object and do map checks.
JSObject* current = object;
while (current != holder) {
depth++;
// Only global objects and objects that do not require access
// checks are allowed in stubs.
ASSERT(current->IsJSGlobalProxy() || !current->IsAccessCheckNeeded());
JSObject* prototype = JSObject::cast(current->GetPrototype());
if (!current->HasFastProperties() &&
!current->IsJSGlobalObject() &&
!current->IsJSGlobalProxy()) {
if (!name->IsSymbol()) {
Object* lookup_result = Heap::LookupSymbol(name);
if (lookup_result->IsFailure()) {
set_failure(Failure::cast(lookup_result));
return reg;
} else {
name = String::cast(lookup_result);
}
}
ASSERT(current->property_dictionary()->FindEntry(name) ==
StringDictionary::kNotFound);
GenerateDictionaryNegativeLookup(masm(),
miss,
reg,
name,
scratch,
extra);
__ ldr(scratch, FieldMemOperand(reg, HeapObject::kMapOffset));
reg = holder_reg; // from now the object is in holder_reg
__ ldr(reg, FieldMemOperand(scratch, Map::kPrototypeOffset));
} else {
// Get the map of the current object.
__ ldr(scratch, FieldMemOperand(reg, HeapObject::kMapOffset));
__ cmp(scratch, Operand(Handle<Map>(current->map())));
// Branch on the result of the map check.
__ b(ne, miss);
// Check access rights to the global object. This has to happen
// after the map check so that we know that the object is
// actually a global object.
if (current->IsJSGlobalProxy()) {
__ CheckAccessGlobalProxy(reg, scratch, miss);
// Restore scratch register to be the map of the object. In the
// new space case below, we load the prototype from the map in
// the scratch register.
__ ldr(scratch, FieldMemOperand(reg, HeapObject::kMapOffset));
}
reg = holder_reg; // from now the object is in holder_reg
if (Heap::InNewSpace(prototype)) {
// The prototype is in new space; we cannot store a reference
// to it in the code. Load it from the map.
__ ldr(reg, FieldMemOperand(scratch, Map::kPrototypeOffset));
} else {
// The prototype is in old space; load it directly.
__ mov(reg, Operand(Handle<JSObject>(prototype)));
}
}
if (save_at_depth == depth) {
__ str(reg, MemOperand(sp));
}
// Go to the next object in the prototype chain.
current = prototype;
}
// Check the holder map.
__ ldr(scratch, FieldMemOperand(reg, HeapObject::kMapOffset));
__ cmp(scratch, Operand(Handle<Map>(current->map())));
__ b(ne, miss);
// Log the check depth.
LOG(IntEvent("check-maps-depth", depth + 1));
// Perform security check for access to the global object and return
// the holder register.
ASSERT(current == holder);
ASSERT(current->IsJSGlobalProxy() || !current->IsAccessCheckNeeded());
if (current->IsJSGlobalProxy()) {
__ CheckAccessGlobalProxy(reg, scratch, miss);
}
// If we've skipped any global objects, it's not enough to verify
// that their maps haven't changed. We also need to check that the
// property cell for the property is still empty.
while (object != holder) {
if (object->IsGlobalObject()) {
current = object;
while (current != holder) {
if (current->IsGlobalObject()) {
Object* cell = GenerateCheckPropertyCell(masm(),
GlobalObject::cast(object),
GlobalObject::cast(current),
name,
scratch,
miss);
if (cell->IsFailure()) {
set_failure(Failure::cast(cell));
return result;
return reg;
}
}
object = JSObject::cast(object->GetPrototype());
current = JSObject::cast(current->GetPrototype());
}
// Return the register containing the holder.
return result;
return reg;
}
......@@ -1053,7 +1280,7 @@ Object* CallStubCompiler::CompileCallField(JSObject* object,
__ b(eq, &miss);
// Do the right check and compute the holder register.
Register reg = CheckPrototypes(object, r0, holder, r1, r3, name, &miss);
Register reg = CheckPrototypes(object, r0, holder, r1, r3, name, &miss, r4);
GenerateFastPropertyLoad(masm(), r1, reg, holder, index);
GenerateCallFunction(masm(), object, arguments(), &miss);
......@@ -1098,7 +1325,7 @@ Object* CallStubCompiler::CompileArrayPushCall(Object* object,
__ b(eq, &miss);
// Check that the maps haven't changed.
CheckPrototypes(JSObject::cast(object), r1, holder, r3, r0, name, &miss);
CheckPrototypes(JSObject::cast(object), r1, holder, r3, r0, name, &miss, r4);
if (object->IsGlobalObject()) {
__ ldr(r3, FieldMemOperand(r1, GlobalObject::kGlobalReceiverOffset));
......@@ -1149,7 +1376,7 @@ Object* CallStubCompiler::CompileArrayPopCall(Object* object,
__ b(eq, &miss);
// Check that the maps haven't changed.
CheckPrototypes(JSObject::cast(object), r1, holder, r3, r0, name, &miss);
CheckPrototypes(JSObject::cast(object), r1, holder, r3, r0, name, &miss, r4);
if (object->IsGlobalObject()) {
__ ldr(r3, FieldMemOperand(r1, GlobalObject::kGlobalReceiverOffset));
......@@ -1247,7 +1474,7 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
// Check that the maps haven't changed.
CheckPrototypes(JSObject::cast(object), r1, holder, r0, r3, name,
depth, &miss);
depth, &miss, r4);
// Patch the receiver on the stack with the global proxy if
// necessary.
......@@ -1270,7 +1497,7 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
GenerateDirectLoadGlobalFunctionPrototype(
masm(), Context::STRING_FUNCTION_INDEX, r0);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r0, holder, r3,
r1, name, &miss);
r1, name, &miss, r4);
}
break;
......@@ -1290,7 +1517,7 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
GenerateDirectLoadGlobalFunctionPrototype(
masm(), Context::NUMBER_FUNCTION_INDEX, r0);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r0, holder, r3,
r1, name, &miss);
r1, name, &miss, r4);
}
break;
}
......@@ -1313,7 +1540,7 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
GenerateDirectLoadGlobalFunctionPrototype(
masm(), Context::BOOLEAN_FUNCTION_INDEX, r0);
CheckPrototypes(JSObject::cast(object->GetPrototype()), r0, holder, r3,
r1, name, &miss);
r1, name, &miss, r4);
}
break;
}
......@@ -1418,7 +1645,7 @@ Object* CallStubCompiler::CompileCallGlobal(JSObject* object,
}
// Check that the maps haven't changed.
CheckPrototypes(object, r0, holder, r3, r1, name, &miss);
CheckPrototypes(object, r0, holder, r3, r1, name, &miss, r4);
// Get the value from the cell.
__ mov(r3, Operand(Handle<JSGlobalPropertyCell>(cell)));
......@@ -1642,7 +1869,7 @@ Object* LoadStubCompiler::CompileLoadNonexistent(String* name,
__ b(eq, &miss);
// Check the maps of the full prototype chain.
CheckPrototypes(object, r0, last, r3, r1, name, &miss);
CheckPrototypes(object, r0, last, r3, r1, name, &miss, r4);
// If the last object in the prototype chain is a global object,
// check that the global property cell is empty.
......@@ -1782,7 +2009,7 @@ Object* LoadStubCompiler::CompileLoadGlobal(JSObject* object,
}
// Check that the map of the global has not changed.
CheckPrototypes(object, r0, holder, r3, r4, name, &miss);
CheckPrototypes(object, r0, holder, r3, r4, name, &miss, r1);
// Get the value from the cell.
__ mov(r3, Operand(Handle<JSGlobalPropertyCell>(cell)));
......
......@@ -121,11 +121,13 @@ static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
const int kInterceptorOrAccessCheckNeededMask =
(1 << Map::kHasNamedInterceptor) | (1 << Map::kIsAccessCheckNeeded);
// Bail out if the receiver has a named interceptor or requires access checks.
__ test(FieldOperand(r0, Map::kBitFieldOffset),
Immediate(kInterceptorOrAccessCheckNeededMask));
__ test_b(FieldOperand(r0, Map::kBitFieldOffset),
kInterceptorOrAccessCheckNeededMask);
__ j(not_zero, miss_label, not_taken);
// Check that receiver is a JSObject.
__ CmpInstanceType(r0, FIRST_JS_OBJECT_TYPE);
__ j(below, miss_label, not_taken);
......
......@@ -525,17 +525,12 @@ void CallICBase::UpdateCaches(LookupResult* lookup,
// Bail out if we didn't find a result.
if (!lookup->IsProperty() || !lookup->IsCacheable()) return;
#ifndef V8_TARGET_ARCH_IA32
// Normal objects only implemented for IA32 by now.
if (HasNormalObjectsInPrototypeChain(lookup, *object)) return;
#else
if (lookup->holder() != *object &&
HasNormalObjectsInPrototypeChain(lookup, object->GetPrototype())) {
// Suppress optimization for prototype chains with slow properties objects
// in the middle.
return;
}
#endif
// Compute the number of arguments.
int argc = target()->arguments_count();
......
......@@ -2322,101 +2322,6 @@ void MacroAssembler::LeaveExitFrame(ExitFrame::Mode mode, int result_size) {
}
Register MacroAssembler::CheckMaps(JSObject* object,
Register object_reg,
JSObject* holder,
Register holder_reg,
Register scratch,
int save_at_depth,
Label* miss) {
// Make sure there's no overlap between scratch and the other
// registers.
ASSERT(!scratch.is(object_reg) && !scratch.is(holder_reg));
// Keep track of the current object in register reg. On the first
// iteration, reg is an alias for object_reg, on later iterations,
// it is an alias for holder_reg.
Register reg = object_reg;
int depth = 0;
if (save_at_depth == depth) {
movq(Operand(rsp, kPointerSize), object_reg);
}
// Check the maps in the prototype chain.
// Traverse the prototype chain from the object and do map checks.
while (object != holder) {
depth++;
// Only global objects and objects that do not require access
// checks are allowed in stubs.
ASSERT(object->IsJSGlobalProxy() || !object->IsAccessCheckNeeded());
JSObject* prototype = JSObject::cast(object->GetPrototype());
if (Heap::InNewSpace(prototype)) {
// Get the map of the current object.
movq(scratch, FieldOperand(reg, HeapObject::kMapOffset));
Cmp(scratch, Handle<Map>(object->map()));
// Branch on the result of the map check.
j(not_equal, miss);
// Check access rights to the global object. This has to happen
// after the map check so that we know that the object is
// actually a global object.
if (object->IsJSGlobalProxy()) {
CheckAccessGlobalProxy(reg, scratch, miss);
// Restore scratch register to be the map of the object.
// We load the prototype from the map in the scratch register.
movq(scratch, FieldOperand(reg, HeapObject::kMapOffset));
}
// The prototype is in new space; we cannot store a reference
// to it in the code. Load it from the map.
reg = holder_reg; // from now the object is in holder_reg
movq(reg, FieldOperand(scratch, Map::kPrototypeOffset));
} else {
// Check the map of the current object.
Cmp(FieldOperand(reg, HeapObject::kMapOffset),
Handle<Map>(object->map()));
// Branch on the result of the map check.
j(not_equal, miss);
// Check access rights to the global object. This has to happen
// after the map check so that we know that the object is
// actually a global object.
if (object->IsJSGlobalProxy()) {
CheckAccessGlobalProxy(reg, scratch, miss);
}
// The prototype is in old space; load it directly.
reg = holder_reg; // from now the object is in holder_reg
Move(reg, Handle<JSObject>(prototype));
}
if (save_at_depth == depth) {
movq(Operand(rsp, kPointerSize), reg);
}
// Go to the next object in the prototype chain.
object = prototype;
}
// Check the holder map.
Cmp(FieldOperand(reg, HeapObject::kMapOffset), Handle<Map>(holder->map()));
j(not_equal, miss);
// Log the check depth.
LOG(IntEvent("check-maps-depth", depth + 1));
// Perform security check for access to the global object and return
// the holder register.
ASSERT(object == holder);
ASSERT(object->IsJSGlobalProxy() || !object->IsAccessCheckNeeded());
if (object->IsJSGlobalProxy()) {
CheckAccessGlobalProxy(reg, scratch, miss);
}
return reg;
}
void MacroAssembler::CheckAccessGlobalProxy(Register holder_reg,
Register scratch,
Label* miss) {
......
......@@ -596,24 +596,6 @@ class MacroAssembler: public Assembler {
// ---------------------------------------------------------------------------
// Inline caching support
// Generates code that verifies that the maps of objects in the
// prototype chain of object hasn't changed since the code was
// generated and branches to the miss label if any map has. If
// necessary the function also generates code for security check
// in case of global object holders. The scratch and holder
// registers are always clobbered, but the object register is only
// clobbered if it the same as the holder register. The function
// returns a register containing the holder - either object_reg or
// holder_reg.
// The function can optionally (when save_at_depth !=
// kInvalidProtoDepth) save the object at the given depth by moving
// it to [rsp + kPointerSize].
Register CheckMaps(JSObject* object, Register object_reg,
JSObject* holder, Register holder_reg,
Register scratch,
int save_at_depth,
Label* miss);
// Generate code for checking access rights - used for security checks
// on access to global objects across environments. The holder register
// is left untouched, but the scratch register and kScratchRegister,
......
......@@ -81,6 +81,113 @@ static void ProbeTable(MacroAssembler* masm,
}
// Helper function used to check that the dictionary doesn't contain
// the property. This function may return false negatives, so miss_label
// must always call a backup property check that is complete.
// This function is safe to call if the receiver has fast properties.
// Name must be a symbol and receiver must be a heap object.
static void GenerateDictionaryNegativeLookup(MacroAssembler* masm,
Label* miss_label,
Register receiver,
String* name,
Register r0,
Register extra) {
ASSERT(name->IsSymbol());
__ IncrementCounter(&Counters::negative_lookups, 1);
__ IncrementCounter(&Counters::negative_lookups_miss, 1);
Label done;
__ movq(r0, FieldOperand(receiver, HeapObject::kMapOffset));
const int kInterceptorOrAccessCheckNeededMask =
(1 << Map::kHasNamedInterceptor) | (1 << Map::kIsAccessCheckNeeded);
// Bail out if the receiver has a named interceptor or requires access checks.
__ testb(FieldOperand(r0, Map::kBitFieldOffset),
Immediate(kInterceptorOrAccessCheckNeededMask));
__ j(not_zero, miss_label);
// Check that receiver is a JSObject.
__ CmpInstanceType(r0, FIRST_JS_OBJECT_TYPE);
__ j(below, miss_label);
// Load properties array.
Register properties = r0;
__ movq(properties, FieldOperand(receiver, JSObject::kPropertiesOffset));
// Check that the properties array is a dictionary.
__ CompareRoot(FieldOperand(properties, HeapObject::kMapOffset),
Heap::kHashTableMapRootIndex);
__ j(not_equal, miss_label);
// Compute the capacity mask.
const int kCapacityOffset =
StringDictionary::kHeaderSize +
StringDictionary::kCapacityIndex * kPointerSize;
// Generate an unrolled loop that performs a few probes before
// giving up.
static const int kProbes = 4;
const int kElementsStartOffset =
StringDictionary::kHeaderSize +
StringDictionary::kElementsStartIndex * kPointerSize;
// If names of slots in range from 1 to kProbes - 1 for the hash value are
// not equal to the name and kProbes-th slot is not used (its name is the
// undefined value), it guarantees the hash table doesn't contain the
// property. It's true even if some slots represent deleted properties
// (their names are the null value).
for (int i = 0; i < kProbes; i++) {
// r0 points to properties hash.
// Compute the masked index: (hash + i + i * i) & mask.
if (extra.is(no_reg)) {
__ push(receiver);
}
Register index = extra.is(no_reg) ? receiver : extra;
// Capacity is smi 2^n.
__ SmiToInteger32(index, FieldOperand(properties, kCapacityOffset));
__ decl(index);
__ and_(index,
Immediate(name->Hash() + StringDictionary::GetProbeOffset(i)));
// Scale the index by multiplying by the entry size.
ASSERT(StringDictionary::kEntrySize == 3);
__ lea(index, Operand(index, index, times_2, 0)); // index *= 3.
Register entity_name = extra.is(no_reg) ? properties : extra;
// Having undefined at this place means the name is not contained.
ASSERT_EQ(kSmiTagSize, 1);
__ movq(entity_name, Operand(properties, index, times_pointer_size,
kElementsStartOffset - kHeapObjectTag));
__ Cmp(entity_name, Factory::undefined_value());
if (extra.is(no_reg)) {
// 'receiver' shares a register with 'entity_name'.
__ pop(receiver);
}
// __ jmp(miss_label);
if (i != kProbes - 1) {
__ j(equal, &done);
// Stop if found the property.
__ Cmp(entity_name, Handle<String>(name));
__ j(equal, miss_label);
if (extra.is(no_reg)) {
// Restore the properties if their register was occupied by the name.
__ movq(properties,
FieldOperand(receiver, JSObject::kPropertiesOffset));
}
} else {
// Give up probing if still not found the undefined value.
__ j(not_equal, miss_label);
}
}
__ bind(&done);
__ DecrementCounter(&Counters::negative_lookups_miss, 1);
}
void StubCompiler::GenerateLoadMiss(MacroAssembler* masm, Code::Kind kind) {
ASSERT(kind == Code::LOAD_IC || kind == Code::KEYED_LOAD_IC);
Code* code = NULL;
......@@ -784,7 +891,7 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
// Check that the maps haven't changed.
CheckPrototypes(JSObject::cast(object), rdx, holder,
rbx, rax, name, depth, &miss);
rbx, rax, name, depth, &miss, rdi);
// Patch the receiver on the stack with the global proxy if
// necessary.
......@@ -807,7 +914,7 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
GenerateDirectLoadGlobalFunctionPrototype(
masm(), Context::STRING_FUNCTION_INDEX, rax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rax, holder,
rbx, rdx, name, &miss);
rbx, rdx, name, &miss, rdi);
}
break;
......@@ -826,7 +933,7 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
GenerateDirectLoadGlobalFunctionPrototype(
masm(), Context::NUMBER_FUNCTION_INDEX, rax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rax, holder,
rbx, rdx, name, &miss);
rbx, rdx, name, &miss, rdi);
}
break;
}
......@@ -847,7 +954,7 @@ Object* CallStubCompiler::CompileCallConstant(Object* object,
GenerateDirectLoadGlobalFunctionPrototype(
masm(), Context::BOOLEAN_FUNCTION_INDEX, rax);
CheckPrototypes(JSObject::cast(object->GetPrototype()), rax, holder,
rbx, rdx, name, &miss);
rbx, rdx, name, &miss, rdi);
}
break;
}
......@@ -902,7 +1009,8 @@ Object* CallStubCompiler::CompileCallField(JSObject* object,
__ JumpIfSmi(rdx, &miss);
// Do the right check and compute the holder register.
Register reg = CheckPrototypes(object, rdx, holder, rbx, rax, name, &miss);
Register reg = CheckPrototypes(object, rdx, holder, rbx, rax,
name, &miss, rdi);
GenerateFastPropertyLoad(masm(), rdi, reg, holder, index);
......@@ -966,7 +1074,8 @@ Object* CallStubCompiler::CompileArrayPushCall(Object* object,
rbx,
rax,
name,
&miss);
&miss,
rdi);
if (argc == 0) {
// Noop, return the length.
......@@ -1119,7 +1228,7 @@ Object* CallStubCompiler::CompileArrayPopCall(Object* object,
CheckPrototypes(JSObject::cast(object), rdx,
holder, rbx,
rax, name, &miss);
rax, name, &miss, rdi);
// Get the elements array of the object.
__ movq(rbx, FieldOperand(rdx, JSArray::kElementsOffset));
......@@ -1288,7 +1397,7 @@ Object* CallStubCompiler::CompileCallGlobal(JSObject* object,
}
// Check that the maps haven't changed.
CheckPrototypes(object, rdx, holder, rbx, rax, name, &miss);
CheckPrototypes(object, rdx, holder, rbx, rax, name, &miss, rdi);
// Get the value from the cell.
__ Move(rdi, Handle<JSGlobalPropertyCell>(cell));
......@@ -1401,7 +1510,7 @@ Object* LoadStubCompiler::CompileLoadNonexistent(String* name,
// Check the maps of the full prototype chain. Also check that
// global property cells up to (but not including) the last object
// in the prototype chain are empty.
CheckPrototypes(object, rax, last, rbx, rdx, name, &miss);
CheckPrototypes(object, rax, last, rbx, rdx, name, &miss, rdi);
// If the last object in the prototype chain is a global object,
// check that the global property cell is empty.
......@@ -1500,7 +1609,7 @@ Object* LoadStubCompiler::CompileLoadGlobal(JSObject* object,
}
// Check that the maps haven't changed.
CheckPrototypes(object, rax, holder, rbx, rdx, name, &miss);
CheckPrototypes(object, rax, holder, rbx, rdx, name, &miss, rdi);
// Get the value from the cell.
__ Move(rbx, Handle<JSGlobalPropertyCell>(cell));
......@@ -2127,36 +2236,137 @@ Register StubCompiler::CheckPrototypes(JSObject* object,
int save_at_depth,
Label* miss,
Register extra) {
// Check that the maps haven't changed.
Register result =
masm()->CheckMaps(object,
object_reg,
holder,
holder_reg,
scratch,
save_at_depth,
miss);
// Make sure there's no overlap between scratch and the other
// registers.
ASSERT(!scratch.is(object_reg) && !scratch.is(holder_reg));
// Keep track of the current object in register reg. On the first
// iteration, reg is an alias for object_reg, on later iterations,
// it is an alias for holder_reg.
Register reg = object_reg;
int depth = 0;
if (save_at_depth == depth) {
__ movq(Operand(rsp, kPointerSize), object_reg);
}
// Check the maps in the prototype chain.
// Traverse the prototype chain from the object and do map checks.
JSObject* current = object;
while (current != holder) {
depth++;
// Only global objects and objects that do not require access
// checks are allowed in stubs.
ASSERT(current->IsJSGlobalProxy() || !current->IsAccessCheckNeeded());
JSObject* prototype = JSObject::cast(current->GetPrototype());
if (!current->HasFastProperties() &&
!current->IsJSGlobalObject() &&
!current->IsJSGlobalProxy()) {
if (!name->IsSymbol()) {
Object* lookup_result = Heap::LookupSymbol(name);
if (lookup_result->IsFailure()) {
set_failure(Failure::cast(lookup_result));
return reg;
} else {
name = String::cast(lookup_result);
}
}
ASSERT(current->property_dictionary()->FindEntry(name) ==
StringDictionary::kNotFound);
GenerateDictionaryNegativeLookup(masm(),
miss,
reg,
name,
scratch,
extra);
__ movq(scratch, FieldOperand(reg, HeapObject::kMapOffset));
reg = holder_reg; // from now the object is in holder_reg
__ movq(reg, FieldOperand(scratch, Map::kPrototypeOffset));
} else if (Heap::InNewSpace(prototype)) {
// Get the map of the current object.
__ movq(scratch, FieldOperand(reg, HeapObject::kMapOffset));
__ Cmp(scratch, Handle<Map>(current->map()));
// Branch on the result of the map check.
__ j(not_equal, miss);
// Check access rights to the global object. This has to happen
// after the map check so that we know that the object is
// actually a global object.
if (current->IsJSGlobalProxy()) {
__ CheckAccessGlobalProxy(reg, scratch, miss);
// Restore scratch register to be the map of the object.
// We load the prototype from the map in the scratch register.
__ movq(scratch, FieldOperand(reg, HeapObject::kMapOffset));
}
// The prototype is in new space; we cannot store a reference
// to it in the code. Load it from the map.
reg = holder_reg; // from now the object is in holder_reg
__ movq(reg, FieldOperand(scratch, Map::kPrototypeOffset));
} else {
// Check the map of the current object.
__ Cmp(FieldOperand(reg, HeapObject::kMapOffset),
Handle<Map>(current->map()));
// Branch on the result of the map check.
__ j(not_equal, miss);
// Check access rights to the global object. This has to happen
// after the map check so that we know that the object is
// actually a global object.
if (current->IsJSGlobalProxy()) {
__ CheckAccessGlobalProxy(reg, scratch, miss);
}
// The prototype is in old space; load it directly.
reg = holder_reg; // from now the object is in holder_reg
__ Move(reg, Handle<JSObject>(prototype));
}
if (save_at_depth == depth) {
__ movq(Operand(rsp, kPointerSize), reg);
}
// Go to the next object in the prototype chain.
current = prototype;
}
// Check the holder map.
__ Cmp(FieldOperand(reg, HeapObject::kMapOffset), Handle<Map>(holder->map()));
__ j(not_equal, miss);
// Log the check depth.
LOG(IntEvent("check-maps-depth", depth + 1));
// Perform security check for access to the global object and return
// the holder register.
ASSERT(current == holder);
ASSERT(current->IsJSGlobalProxy() || !current->IsAccessCheckNeeded());
if (current->IsJSGlobalProxy()) {
__ CheckAccessGlobalProxy(reg, scratch, miss);
}
// If we've skipped any global objects, it's not enough to verify
// that their maps haven't changed. We also need to check that the
// property cell for the property is still empty.
while (object != holder) {
if (object->IsGlobalObject()) {
current = object;
while (current != holder) {
if (current->IsGlobalObject()) {
Object* cell = GenerateCheckPropertyCell(masm(),
GlobalObject::cast(object),
GlobalObject::cast(current),
name,
scratch,
miss);
if (cell->IsFailure()) {
set_failure(Failure::cast(cell));
return result;
return reg;
}
}
object = JSObject::cast(object->GetPrototype());
current = JSObject::cast(current->GetPrototype());
}
// Return the register containing the holder.
return result;
return reg;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment