From 968a52443288867548aa8ac28de9e2e15d04db02 Mon Sep 17 00:00:00 2001 From: "sgjesse@chromium.org" Date: Thu, 22 Apr 2010 07:18:30 +0000 Subject: [PATCH] Add inlining of property load on ARM Generate inlined named property load for in-object properties. This uses the same mechanism as on the Intel platforms with the map check and load instruction of the inlined code being patched by the inline cache code. The map check is patched through the normal constant pool patching and the load instruction is patched in place. Review URL: http://codereview.chromium.org/1715003 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@4468 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/arm/assembler-arm.cc | 42 ++++++++- src/arm/assembler-arm.h | 15 +++- src/arm/codegen-arm.cc | 120 +++++++++++++++++++------- src/arm/codegen-arm.h | 4 + src/arm/full-codegen-arm.cc | 30 +++++-- src/arm/ic-arm.cc | 57 ++++++++++-- src/arm/virtual-frame-arm.cc | 14 +++ src/arm/virtual-frame-arm.h | 5 ++ test/mjsunit/debug-stepin-accessor.js | 2 +- 9 files changed, 238 insertions(+), 51 deletions(-) diff --git a/src/arm/assembler-arm.cc b/src/arm/assembler-arm.cc index f736a1d3c9..759030cf88 100644 --- a/src/arm/assembler-arm.cc +++ b/src/arm/assembler-arm.cc @@ -349,6 +349,44 @@ void Assembler::Align(int m) { } +bool Assembler::IsB(Instr instr) { + return (instr & (B27 | B25)) == (B27 | B25); +} + + +int Assembler::GetBOffset(Instr instr) { + ASSERT(IsB(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. // @@ -372,10 +410,10 @@ int Assembler::target_at(int pos) { } 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; } diff --git a/src/arm/assembler-arm.h b/src/arm/assembler-arm.h index 82142cca5e..12cde0a22d 100644 --- a/src/arm/assembler-arm.h +++ b/src/arm/assembler-arm.h @@ -958,14 +958,21 @@ class Assembler : public Malloced { int current_position() const { return current_position_; } int current_statement_position() const { return current_statement_position_; } + // Read/patch instructions + static Instr instr_at(byte* pc) { return *reinterpret_cast(pc); } + static void instr_at_put(byte* pc, Instr instr) { + *reinterpret_cast(pc) = instr; + } + static bool IsB(Instr instr); + static int GetBOffset(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(pc); } - void instr_at_put(byte* pc, Instr instr) { - *reinterpret_cast(pc) = instr; - } Instr instr_at(int pos) { return *reinterpret_cast(buffer_ + pos); } void instr_at_put(int pos, Instr instr) { *reinterpret_cast(buffer_ + pos) = instr; diff --git a/src/arm/codegen-arm.cc b/src/arm/codegen-arm.cc index b4fc379a27..13e9c6e1ff 100644 --- a/src/arm/codegen-arm.cc +++ b/src/arm/codegen-arm.cc @@ -358,12 +358,9 @@ void CodeGenerator::Generate(CompilationInfo* info) { Label check_exit_codesize; masm_->bind(&check_exit_codesize); #endif - - { // NOLINT - // Make sure that the constant pool is not emitted inside of the return - // sequence. - Assembler::BlockConstPoolScope block_const_pool(masm_); - + // 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(); @@ -393,6 +390,7 @@ void CodeGenerator::Generate(CompilationInfo* info) { // 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(); @@ -2940,20 +2938,13 @@ void CodeGenerator::LoadFromGlobalSlotCheckExtensions(Slot* slot, __ bind(&fast); } - // All extension objects were empty and it is safe to use a global - // load IC call. - Handle 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(); } @@ -4935,6 +4926,85 @@ void CodeGenerator::VisitCompareOperation(CompareOperation* node) { } +class DeferredReferenceGetNamedValue: public DeferredCode { + public: + explicit DeferredReferenceGetNamedValue(Handle name) : name_(name) { + set_comment("[ DeferredReferenceGetNamedValue"); + } + + virtual void Generate(); + + private: + Handle name_; +}; + + +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 ic(Builtins::builtin(Builtins::LoadIC_Initialize)); + __ Call(ic, RelocInfo::CODE_TARGET); + // The call must be followed by a b instruction to indicate that the inobject + // property case was inlined. Jumping back from the deferred code ensures + // that. +} + + +void CodeGenerator::EmitNamedLoad(Handle 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 ic(Builtins::builtin(Builtins::KeyedLoadIC_Initialize)); @@ -4991,19 +5061,11 @@ void Reference::GetValue() { } case NAMED: { - VirtualFrame* frame = cgen_->frame(); - Comment cmnt(masm, "[ Load from named Property"); - Handle name(GetName()); Variable* var = expression_->AsVariableProxy()->AsVariable(); - Handle 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; } diff --git a/src/arm/codegen-arm.h b/src/arm/codegen-arm.h index 5abd814e39..0cb297de70 100644 --- a/src/arm/codegen-arm.h +++ b/src/arm/codegen-arm.h @@ -287,6 +287,10 @@ class CodeGenerator: public AstVisitor { // 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 remain there. + void EmitNamedLoad(Handle 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); diff --git a/src/arm/full-codegen-arm.cc b/src/arm/full-codegen-arm.cc index 56df8cfdf9..4e3996fca2 100644 --- a/src/arm/full-codegen-arm.cc +++ b/src/arm/full-codegen-arm.cc @@ -199,12 +199,9 @@ void FullCodeGenerator::EmitReturnSequence(int position) { Label check_exit_codesize; masm_->bind(&check_exit_codesize); #endif - - { // NOLINT - // Make sure that the constant pool is not emitted inside of the return - // sequence. - Assembler::BlockConstPoolScope block_const_pool(masm_); - + // 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; @@ -703,7 +700,12 @@ void FullCodeGenerator::EmitVariableLoad(Variable* var, __ push(ip); __ mov(r2, Operand(var->name())); Handle ic(Builtins::builtin(Builtins::LoadIC_Initialize)); - __ Call(ic, RelocInfo::CODE_TARGET_CONTEXT); + { Assembler::BlockConstPoolScope block_const_pool(masm_); + __ Call(ic, RelocInfo::CODE_TARGET_CONTEXT); + // A B instruction following the call signals that the load was inlined. + // Ensure that there is not a B instruction here. + __ nop(); + } DropAndApply(1, context, r0); } else if (slot != NULL && slot->type() == Slot::LOOKUP) { @@ -1001,7 +1003,12 @@ void FullCodeGenerator::EmitNamedPropertyLoad(Property* prop) { Literal* key = prop->key()->AsLiteral(); __ mov(r2, Operand(key->handle())); Handle ic(Builtins::builtin(Builtins::LoadIC_Initialize)); - __ Call(ic, RelocInfo::CODE_TARGET); + { Assembler::BlockConstPoolScope block_const_pool(masm_); + __ Call(ic, RelocInfo::CODE_TARGET); + // A B instruction following the call signals that the load was inlined. + // Ensure that there is not a B instruction here. + __ nop(); + } } @@ -1438,7 +1445,12 @@ void FullCodeGenerator::VisitUnaryOperation(UnaryOperation* expr) { Handle ic(Builtins::builtin(Builtins::LoadIC_Initialize)); // Use a regular load, not a contextual load, to avoid a reference // error. - __ Call(ic, RelocInfo::CODE_TARGET); + { Assembler::BlockConstPoolScope block_const_pool(masm_); + __ Call(ic, RelocInfo::CODE_TARGET); + // A B instruction following the call signals that the load was + // inlined. Ensure that there is not a B instruction here. + __ nop(); + } __ str(r0, MemOperand(sp)); } else if (proxy != NULL && proxy->var()->slot() != NULL && diff --git a/src/arm/ic-arm.cc b/src/arm/ic-arm.cc index 0ac42173ed..f60aa627b4 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,64 @@ void LoadIC::GenerateMiss(MacroAssembler* masm) { } -// 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 a B instruction then this is + // not related to an inlined in-object property load. The B instructions is + // located just after the call to the IC in the deferred code handling the + // miss in the inlined code. All other calls to a load IC should ensure there + // in no B instruction directly following the call. + Address address_after_call = address + Assembler::kCallTargetAddressOffset; + Instr instr_after_call = Assembler::instr_at(address_after_call); + if (!Assembler::IsB(instr_after_call)) return false; + + // Find the end of the inlined code for handling the load. + int b_offset = + Assembler::GetBOffset(instr_after_call) + Assembler::kPcLoadDelta; + ASSERT(b_offset < 0); // Jumping back from deferred code. + Address inline_end_address = address_after_call + b_offset; + + // Patch the offset of the property load instruction (ldr r0, [r1, #+XXX]). + 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
(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; } diff --git a/src/arm/virtual-frame-arm.cc b/src/arm/virtual-frame-arm.cc index a4c5484eb3..096847f81b 100644 --- a/src/arm/virtual-frame-arm.cc +++ b/src/arm/virtual-frame-arm.cc @@ -299,6 +299,20 @@ void VirtualFrame::InvokeBuiltin(Builtins::JavaScript id, } +void VirtualFrame::CallLoadIC(RelocInfo::Mode mode, bool load_inlined) { + // If a nop is generated later make sure the it follows the call directly. + Assembler::BlockConstPoolScope block_const_pool(masm()); + + Handle ic(Builtins::builtin(Builtins::LoadIC_Initialize)); + CallCodeObject(ic, mode, 0); + if (!load_inlined) { + // A B instruction following the call signals that the load was inlined. + // Ensure that there is not a B instruction here. + __ nop(); + } +} + + void VirtualFrame::CallCodeObject(Handle code, RelocInfo::Mode rmode, int dropped_args) { diff --git a/src/arm/virtual-frame-arm.h b/src/arm/virtual-frame-arm.h index c5a7fbbe01..2cd95ec8e1 100644 --- a/src/arm/virtual-frame-arm.h +++ b/src/arm/virtual-frame-arm.h @@ -308,6 +308,11 @@ class VirtualFrame : public ZoneObject { InvokeJSFlags flag, int arg_count); + // Call load IC. Receiver on stack and property name in r2. Result returned in + // r0. If load_inlined is false the code generated will make sure that the IC + // handling will not see this load as having an inlined counterpart. + void CallLoadIC(RelocInfo::Mode mode, bool load_inlined = false); + // 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/test/mjsunit/debug-stepin-accessor.js b/test/mjsunit/debug-stepin-accessor.js index 8b24c3c14f..2e593b2863 100644 --- a/test/mjsunit/debug-stepin-accessor.js +++ b/test/mjsunit/debug-stepin-accessor.js @@ -36,7 +36,7 @@ var expected_source_line_text = null; var expected_function_name = null; // Simple debug event handler which first time will cause 'step in' action -// to get into g.call and than check that execution is pauesed inside +// to get into g.call and than check that execution is stopped inside // function 'g'. function listener(event, exec_state, event_data, data) { try {