| // Copyright 2017, VIXL authors |
| // All rights reserved. |
| // |
| // Redistribution and use in source and binary forms, with or without |
| // modification, are permitted provided that the following conditions are met: |
| // |
| // * Redistributions of source code must retain the above copyright notice, |
| // this list of conditions and the following disclaimer. |
| // * Redistributions in binary form must reproduce the above copyright notice, |
| // this list of conditions and the following disclaimer in the documentation |
| // and/or other materials provided with the distribution. |
| // * Neither the name of ARM Limited nor the names of its contributors may be |
| // used to endorse or promote products derived from this software without |
| // specific prior written permission. |
| // |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND |
| // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| #include "test-pool-manager.h" |
| |
| #include <stdio.h> |
| |
| #include "pool-manager-impl.h" |
| #include "pool-manager.h" |
| #include "test-runner.h" |
| |
| #define TEST(Name) TEST_(POOL_MANAGER_##Name) |
| |
| #define IF_VERBOSE(exp) \ |
| if (Test::verbose()) exp |
| |
| #define BUFFER_ALIGNMENT 16 |
| |
| using namespace vixl; |
| |
| static int Random() { return static_cast<int>(std::abs(mrand48())); } |
| |
| static int RandomObjectID(size_t num_objects) { return Random() % num_objects; } |
| |
| static int RandomObjectSize() { return 1 + Random() % 256; } |
| |
| static int RandomObjectAlignment(int size) { |
| const int limit = static_cast<int>(floor(log2(BUFFER_ALIGNMENT))); |
| int log2Size = static_cast<int>(floor(log2(size))); |
| // Restrict alignment due to buffer alignment. |
| log2Size = std::min(log2Size, limit); |
| return (1 << (Random() % (1 + log2Size))); |
| } |
| |
| // The size of the instruction. |
| static int RandomReferenceSize() { return (Random() % 2) ? 2 : 4; } |
| |
| // The alignment of an instruction is either 2 or 4. |
| static int RandomInstructionAlignment() { return (Random() % 2) ? 2 : 4; } |
| |
| static int32_t RandomMinOffset() { |
| const int N = 3; |
| static const int offsets[N] = {0, 2, 4}; |
| return offsets[Random() % N]; |
| } |
| |
| static int32_t RandomMaxOffset() { |
| const int N = 5; |
| static const int offsets[N] = {255, 1020, 1024, 4096, 16384}; |
| return offsets[Random() % N]; |
| } |
| |
| static int32_t RandomBranchMaxOffset() { |
| const int N = 10; |
| // The maximum offsets used for testing are taken from A32 and T32. |
| static const int offsets[N] = |
| {126, 254, 255, 1020, 1024, 2046, 4095, 1048574, 16777214, 33554428}; |
| return offsets[Random() % N]; |
| } |
| |
| static int RandomPCIncrement() { |
| // A multiple of two. |
| return 2 * (Random() % 4 + 1); |
| } |
| |
| class TestObject : public LocationBase<int32_t> { |
| public: |
| TestObject(int size, int alignment, int id = 0) |
| : LocationBase(0 /*type*/, size, alignment), id_(id) {} |
| |
| void EmitPoolObject(MacroAssemblerInterface *masm) VIXL_OVERRIDE { |
| USE(masm); |
| } |
| |
| bool ShouldDeletePoolObjectOnPlacement() const VIXL_OVERRIDE { return true; } |
| |
| // Update the references to this object. |
| void ResolveReferences(internal::AssemblerBase *assembler) VIXL_OVERRIDE { |
| int32_t location = GetLocation(); |
| USE(assembler); |
| for (std::vector<ForwardReference<int32_t> *>::iterator iter = |
| references_.begin(); |
| iter != references_.end();) { |
| ForwardReference<int32_t> *ref = *iter; |
| VIXL_ASSERT(ref->LocationIsEncodable(location)); |
| delete ref; |
| iter = references_.erase(iter); |
| } |
| IF_VERBOSE(printf("Placed object %d at location: 0x%x (%u)\n", |
| id_, |
| location, |
| location)); |
| } |
| |
| void AddReference(ForwardReference<int32_t> *ref) { |
| references_.push_back(ref); |
| } |
| |
| int GetID() { return id_; } |
| |
| static TestObject *CreateRandom(int id) { |
| int size = RandomObjectSize(); |
| int alignment = RandomObjectAlignment(size); |
| IF_VERBOSE(printf("Object %d -> size = %d, alignment = %d\n", |
| id, |
| size, |
| alignment)); |
| return new TestObject(size, alignment, id); |
| } |
| |
| private: |
| // Store pointers to ForwardReference objects - TestObject is responsible |
| // for deleting them. |
| std::vector<ForwardReference<int32_t> *> references_; |
| // Object id used for debugging. |
| int id_; |
| }; |
| |
| class TestBranchObject : public LocationBase<int32_t> { |
| public: |
| TestBranchObject(int size, int alignment, int id = 0) |
| : LocationBase(1 /* type */, size, alignment), id_(id) {} |
| |
| bool UsePoolObjectEmissionMargin() const VIXL_OVERRIDE { return true; } |
| int32_t GetPoolObjectEmissionMargin() const VIXL_OVERRIDE { |
| return 1 * KBytes; |
| } |
| |
| // Do nothing for now. |
| void EmitPoolObject(MacroAssemblerInterface *masm) VIXL_OVERRIDE { |
| USE(masm); |
| } |
| |
| bool ShouldDeletePoolObjectOnPlacement() const VIXL_OVERRIDE { return false; } |
| |
| virtual void UpdatePoolObject(PoolObject<int32_t> *object) VIXL_OVERRIDE { |
| // Reference from the last emitted veneer: |
| int32_t min = location_ + min_offset_; |
| int32_t max = location_ + max_offset_; |
| // The alignment that the new "veneer" requires of the label. |
| int reference_alignment = RandomInstructionAlignment(); |
| reference_alignment = |
| std::max(reference_alignment, GetPoolObjectAlignment()); |
| ForwardReference<int32_t> *ref = |
| new ForwardReference<int32_t>(location_, |
| 4 /*size*/, |
| min, |
| max, |
| reference_alignment); |
| AddReference(ref); |
| object->Update(min, max, reference_alignment); |
| } |
| |
| // Update the references to this object. |
| void ResolveReferences(internal::AssemblerBase *assembler) VIXL_OVERRIDE { |
| int32_t location = GetLocation(); |
| USE(assembler); |
| for (std::vector<ForwardReference<int32_t> *>::iterator iter = |
| references_.begin(); |
| iter != references_.end();) { |
| ForwardReference<int32_t> *ref = *iter; |
| VIXL_ASSERT(ref->LocationIsEncodable(location)); |
| delete ref; |
| iter = references_.erase(iter); |
| } |
| IF_VERBOSE(printf("Veneer %d placed at location: 0x%x (%u)\n", |
| id_, |
| location, |
| location)); |
| } |
| |
| void AddReference(ForwardReference<int32_t> *ref) { |
| references_.push_back(ref); |
| } |
| |
| virtual int GetMaxAlignment() const VIXL_OVERRIDE { |
| int max_alignment = GetPoolObjectAlignment(); |
| for (std::vector<ForwardReference<int32_t> *>::const_iterator iter = |
| references_.begin(); |
| iter != references_.end(); |
| ++iter) { |
| const ForwardReference<int32_t> *ref = *iter; |
| if (ref->GetAlignment() > max_alignment) |
| max_alignment = ref->GetAlignment(); |
| } |
| return max_alignment; |
| } |
| virtual int32_t GetMinLocation() const VIXL_OVERRIDE { |
| int32_t min_location = 0; |
| for (std::vector<ForwardReference<int32_t> *>::const_iterator iter = |
| references_.begin(); |
| iter != references_.end(); |
| ++iter) { |
| const ForwardReference<int32_t> *ref = *iter; |
| if (ref->GetMinLocation() > min_location) |
| min_location = ref->GetMinLocation(); |
| } |
| return min_location; |
| } |
| |
| int GetID() { return id_; } |
| |
| static TestBranchObject *CreateRandom(int id) { |
| int size = RandomReferenceSize(); |
| int alignment = size; |
| IF_VERBOSE(printf("Object %d -> size = %d, alignment = %d\n", |
| id, |
| size, |
| alignment)); |
| return new TestBranchObject(size, alignment, id); |
| } |
| |
| private: |
| // Store pointers to ForwardReference objects - TestBranchObject is |
| // responsible for deleting them. |
| std::vector<ForwardReference<int32_t> *> references_; |
| // Object id used for debugging. |
| int id_; |
| |
| // These are the min and max offsets of the type of branch used for the |
| // veneer. |
| static const int32_t min_offset_ = 0; |
| static const int32_t max_offset_ = 16 * 1024 * 1024; |
| }; |
| |
| // MacroAssembler implementation that does nothing but print in verbose mode. |
| class TestMacroAssembler : public MacroAssemblerInterface { |
| public: |
| TestMacroAssembler() : assembler_(128) {} |
| |
| void EmitPoolHeader() VIXL_OVERRIDE { |
| IF_VERBOSE(printf("[MASM] Emitting pool header.\n")); |
| } |
| void EmitPoolFooter() VIXL_OVERRIDE { |
| IF_VERBOSE(printf("[MASM] Emitting pool footer.\n")); |
| } |
| void EmitPaddingBytes(int n) VIXL_OVERRIDE { |
| IF_VERBOSE(printf("[MASM] Added %d bytes of padding.\n", n)); |
| } |
| void EmitNopBytes(int n) VIXL_OVERRIDE { |
| IF_VERBOSE(printf("[MASM] Added %d bytes of NOPs.\n", n)); |
| } |
| bool ArePoolsBlocked() const VIXL_OVERRIDE { return false; } |
| bool AllowMacroInstructions() const VIXL_OVERRIDE { return false; } |
| void SetAllowMacroInstructions(bool allow) VIXL_OVERRIDE { USE(allow); } |
| |
| void BlockPools() VIXL_OVERRIDE {} |
| void ReleasePools() VIXL_OVERRIDE {} |
| void EnsureEmitPoolsFor(size_t) VIXL_OVERRIDE {} |
| internal::AssemblerBase *AsAssemblerBase() VIXL_OVERRIDE { |
| return &assembler_; |
| } |
| |
| private: |
| internal::AssemblerBase assembler_; |
| }; |
| |
| // Used for debugging. |
| namespace vixl { |
| template <> |
| void PoolManager<int32_t>::DumpCurrentState(int32_t pc) const { |
| IF_VERBOSE( |
| printf("Number of objects: %d\n", static_cast<int>(objects_.size()))); |
| IF_VERBOSE(printf("Current pc = 0x%x (%d)\n", pc, pc)); |
| |
| for (int i = 0; i < static_cast<int>(objects_.size()); ++i) { |
| const PoolObject<int32_t> &object = objects_[i]; |
| IF_VERBOSE( |
| printf("Object %d -> size = %d, alignment = %d, range = (%d,%d)\n", |
| i, |
| object.label_base_->GetPoolObjectSizeInBytes(), |
| object.alignment_, |
| object.min_location_, |
| object.max_location_)); |
| } |
| } |
| } |
| |
| // Basic test - checks that emitting a very simple pool works. |
| TEST(Basic) { |
| TestMacroAssembler masm; |
| |
| PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 2 /*header_alignment*/, |
| BUFFER_ALIGNMENT); |
| TestObject object1(4 /*size*/, 4 /*alignment*/); |
| TestObject object2(128 /*size*/, 4 /*alignment*/); |
| ForwardReference<int32_t> *ref1_obj1 = |
| new ForwardReference<int32_t>(0 /*location*/, 2 /*size*/, 0, 200); |
| ForwardReference<int32_t> *ref2_obj1 = |
| new ForwardReference<int32_t>(2 /*location*/, 2 /*size*/, 2, 202); |
| ForwardReference<int32_t> *ref3_obj1 = |
| new ForwardReference<int32_t>(4 /*location*/, 2 /*size*/, 4, 204); |
| object1.AddReference(ref1_obj1); |
| object1.AddReference(ref2_obj1); |
| object1.AddReference(ref3_obj1); |
| ForwardReference<int32_t> *ref1_obj2 = |
| new ForwardReference<int32_t>(8 /*location*/, 2 /*size*/, 8, 500); |
| ForwardReference<int32_t> *ref2_obj2 = |
| new ForwardReference<int32_t>(12 /*location*/, 4 /*size*/, 12, 300); |
| ForwardReference<int32_t> *ref3_obj2 = |
| new ForwardReference<int32_t>(16 /*location*/, 4 /*size*/, 16, 400); |
| object2.AddReference(ref1_obj2); |
| object2.AddReference(ref2_obj2); |
| object2.AddReference(ref3_obj2); |
| |
| pool_manager.AddObjectReference(ref1_obj1, &object1); |
| pool_manager.AddObjectReference(ref2_obj1, &object1); |
| pool_manager.AddObjectReference(ref3_obj1, &object1); |
| pool_manager.AddObjectReference(ref1_obj2, &object2); |
| pool_manager.AddObjectReference(ref2_obj2, &object2); |
| pool_manager.AddObjectReference(ref3_obj2, &object2); |
| |
| pool_manager.Emit(&masm, 20); |
| } |
| |
| static ForwardReference<int32_t> *CreateReference(int id, |
| int32_t pc, |
| int size, |
| int32_t min_offset, |
| int32_t max_offset, |
| int alignment) { |
| IF_VERBOSE(printf( |
| "About to add a new reference to object %d with min location = %d, max " |
| "location = %d, alignment = %d, size = %d\n", |
| id, |
| min_offset + pc, |
| max_offset + pc, |
| alignment, |
| size)); |
| return new ForwardReference<int32_t>(pc, |
| size, |
| min_offset + pc, |
| max_offset + pc, |
| alignment); |
| } |
| |
| // Fuzz test that uses literal-like objects, that get deleted when they are |
| // placed. |
| TEST(FuzzObjectDeletedWhenPlaced) { |
| TestMacroAssembler masm; |
| PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 2 /*header_alignment*/, |
| BUFFER_ALIGNMENT); |
| |
| const int kObjectNum = 100; |
| std::vector<TestObject *> objects; |
| |
| // Create objects. |
| for (int i = 0; i < kObjectNum; ++i) { |
| objects.push_back(TestObject::CreateRandom(i)); |
| } |
| |
| int32_t pc = 0; |
| for (int i = 0; !objects.empty(); ++i) { |
| IF_VERBOSE(printf("PC = 0x%x (%d)\n", pc, pc)); |
| int32_t pc_increment = RandomPCIncrement(); |
| IF_VERBOSE(printf("Attempting to increment PC by %d\n", pc_increment)); |
| if (pool_manager.MustEmit(pc, pc_increment)) { |
| pc = pool_manager.Emit(&masm, pc, pc_increment); |
| } |
| pc += pc_increment; |
| // Pick an object, randomly. |
| TestObject *object = objects[RandomObjectID(objects.size())]; |
| int32_t min_offset = RandomMinOffset(); |
| int32_t max_offset = RandomMaxOffset(); |
| int32_t size = RandomReferenceSize(); |
| int32_t alignment = |
| RandomObjectAlignment(object->GetPoolObjectSizeInBytes()); |
| ForwardReference<int32_t> *ref = CreateReference(object->GetID(), |
| pc, |
| size, |
| min_offset, |
| max_offset, |
| alignment); |
| if (pool_manager.MustEmit(pc, size, ref, object)) { |
| pc = pool_manager.Emit(&masm, pc, size, ref, object); |
| delete ref; |
| // We must recreate the reference, the PC has changed, but only if |
| // it still is a forward reference. |
| if (!object->IsBound()) { |
| ref = CreateReference(object->GetID(), |
| pc, |
| size, |
| min_offset, |
| max_offset, |
| alignment); |
| } |
| } |
| IF_VERBOSE(printf("Incrementing PC by size of reference (%d).\n", size)); |
| pc += size; |
| // We only need to track the reference if it's a forward reference. |
| if (!object->IsBound()) { |
| object->AddReference(ref); |
| pool_manager.AddObjectReference(ref, object); |
| } |
| VIXL_ASSERT(!pool_manager.MustEmit(pc - 1)); |
| // Remove bound objects. |
| for (std::vector<TestObject *>::iterator iter = objects.begin(); |
| iter != objects.end();) { |
| TestObject *object = *iter; |
| if (object->IsBound()) { |
| delete object; |
| iter = objects.erase(iter); |
| } else { |
| ++iter; |
| } |
| } |
| } |
| |
| pool_manager.Emit(&masm, pc); |
| } |
| |
| // Fuzz test that uses veneer-like objects, that get updated when they are |
| // placed and get deleted when they are bound by the user. |
| TEST(FuzzObjectUpdatedWhenPlaced) { |
| TestMacroAssembler masm; |
| PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 2 /*header_alignment*/, |
| BUFFER_ALIGNMENT); |
| const int kObjectNum = 1000; |
| std::vector<TestBranchObject *> objects; |
| |
| // Create objects. |
| for (int i = 0; i < kObjectNum; ++i) { |
| objects.push_back(TestBranchObject::CreateRandom(i)); |
| } |
| |
| int32_t pc = 0; |
| for (int i = 0; !objects.empty(); ++i) { |
| IF_VERBOSE(printf("PC = 0x%x (%d)\n", pc, pc)); |
| |
| int32_t pc_increment = RandomPCIncrement(); |
| IF_VERBOSE(printf("Attempting to increment PC by %d\n", pc_increment)); |
| |
| if (pool_manager.MustEmit(pc, pc_increment)) { |
| pc = pool_manager.Emit(&masm, pc, pc_increment); |
| } |
| pc += pc_increment; |
| |
| // Pick a random object. |
| TestBranchObject *object = objects[RandomObjectID(objects.size())]; |
| int32_t min_offset = RandomMinOffset(); |
| int32_t max_offset = RandomBranchMaxOffset(); |
| int32_t size = RandomReferenceSize(); |
| int32_t alignment = |
| RandomObjectAlignment(object->GetPoolObjectSizeInBytes()); |
| ForwardReference<int32_t> *ref = CreateReference(object->GetID(), |
| pc, |
| size, |
| min_offset, |
| max_offset, |
| alignment); |
| if (pool_manager.MustEmit(pc, size, ref, object)) { |
| pc = pool_manager.Emit(&masm, pc, size); |
| delete ref; |
| // We must recreate the reference, the PC has changed. |
| ref = CreateReference(object->GetID(), |
| pc, |
| size, |
| min_offset, |
| max_offset, |
| alignment); |
| } |
| IF_VERBOSE(printf("Incrementing PC by size of reference (%d).\n", size)); |
| pc += size; |
| object->AddReference(ref); |
| pool_manager.AddObjectReference(ref, object); |
| VIXL_ASSERT(!pool_manager.MustEmit(pc - 1)); |
| |
| // Pick another random label to bind. |
| const int kProbabilityToBind = 20; |
| if ((Random() % 100) < kProbabilityToBind) { |
| TestBranchObject *object = objects[RandomObjectID(objects.size())]; |
| // Binding can cause the pool emission, so check if we need to emit |
| // the pools. The actual backends will know the max alignment we |
| // might need here, so can simplify the check (won't need to check |
| // the object references). |
| int max_padding = object->GetMaxAlignment() - 1; |
| if (pool_manager.MustEmit(pc, max_padding)) { |
| pc = pool_manager.Emit(&masm, pc, max_padding); |
| } |
| pc = pool_manager.Bind(&masm, object, pc); |
| } |
| |
| // Remove bound objects. |
| for (std::vector<TestBranchObject *>::iterator iter = objects.begin(); |
| iter != objects.end();) { |
| TestBranchObject *object = *iter; |
| if (object->IsBound()) { |
| delete object; |
| iter = objects.erase(iter); |
| } else { |
| ++iter; |
| } |
| } |
| } |
| |
| pool_manager.Emit(&masm, pc); |
| } |
| |
| // Test that binding an unused label works. |
| TEST(BindUnusedLabel) { |
| TestMacroAssembler masm; |
| |
| PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 2 /*header_alignment*/, |
| BUFFER_ALIGNMENT); |
| TestBranchObject *object = new TestBranchObject(4 /*size*/, 4 /*alignment*/); |
| int32_t pc = 0; |
| pool_manager.Bind(&masm, object, pc); |
| delete object; |
| } |
| |
| // Test that binding a label adds necessary padding. |
| TEST(BindLabelNeedsPadding) { |
| TestMacroAssembler masm; |
| |
| PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 2 /*header_alignment*/, |
| BUFFER_ALIGNMENT); |
| |
| // Label that needs padding because of the minimum location of the reference. |
| TestBranchObject *object = new TestBranchObject(4 /*size*/, 2 /*alignment*/); |
| ForwardReference<int32_t> *ref = |
| new ForwardReference<int32_t>(0 /*location*/, |
| 2 /*size*/, |
| 4 /*min_location*/, |
| 500 /*max_location*/); |
| object->AddReference(ref); |
| pool_manager.AddObjectReference(ref, object); |
| int32_t pc = 2; |
| pc = pool_manager.Bind(&masm, object, pc); |
| VIXL_ASSERT(pc == 4); |
| delete object; |
| |
| // Label that needs padding because of the alignment of the object. |
| object = new TestBranchObject(4 /*size*/, 4 /*alignment*/); |
| ref = new ForwardReference<int32_t>(0 /*location*/, |
| 2 /*size*/, |
| 0 /*min_location*/, |
| 500 /*max_location*/); |
| object->AddReference(ref); |
| pool_manager.AddObjectReference(ref, object); |
| |
| pc = 2; |
| pc = pool_manager.Bind(&masm, object, pc); |
| VIXL_ASSERT(pc == 4); |
| delete object; |
| |
| // Label that needs padding because of the alignment of the reference. |
| object = new TestBranchObject(4 /*size*/, 1 /*alignment*/); |
| ref = new ForwardReference<int32_t>(0 /*location*/, |
| 2 /*size*/, |
| 0 /*min_location*/, |
| 500 /*max_location*/, |
| 4 /*alignment*/); |
| object->AddReference(ref); |
| pool_manager.AddObjectReference(ref, object); |
| |
| pc = 2; |
| pc = pool_manager.Bind(&masm, object, pc); |
| VIXL_ASSERT(pc == 4); |
| delete object; |
| } |
| |
| // This test checks that when we omit the pool header, we insert any padding |
| // needed in order to meet the minimum location of the first object. |
| TEST(PoolWithoutHeaderMinLocation) { |
| TestMacroAssembler masm; |
| |
| PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 2 /*header_alignment*/, |
| BUFFER_ALIGNMENT); |
| int object_size = 4; |
| int object_alignment = 1; // Do not restrict alignment for this test. |
| int min_location = 4; // We emit the pool at location 2, so need padding. |
| int max_location = 500; |
| TestObject object(object_size, object_alignment); |
| ForwardReference<int32_t> *ref = new ForwardReference<int32_t>(0 /*location*/, |
| 2 /*size*/, |
| min_location, |
| max_location); |
| object.AddReference(ref); |
| pool_manager.AddObjectReference(ref, &object); |
| |
| int32_t new_pc = pool_manager.Emit(&masm, |
| 2, |
| 0, /* no new code added */ |
| NULL, |
| NULL, |
| PoolManager<int32_t>::kNoBranchRequired); |
| USE(new_pc); |
| VIXL_ASSERT(new_pc == min_location + object_size); |
| } |
| |
| // This test checks that when we omit the pool header, we insert any padding |
| // needed in order to meet the alignment of the first object. |
| TEST(PoolWithoutHeaderAlignment) { |
| TestMacroAssembler masm; |
| |
| PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 2 /*header_alignment*/, |
| BUFFER_ALIGNMENT); |
| int object_size = 4; |
| int object_alignment = 4; // We emit the pool at location 2, so need padding. |
| int min_location = 0; // Do not restrict this for this test. |
| int max_location = 500; |
| TestObject object(object_size, object_alignment); |
| ForwardReference<int32_t> *ref = new ForwardReference<int32_t>(0 /*location*/, |
| 2 /*size*/, |
| min_location, |
| max_location); |
| object.AddReference(ref); |
| pool_manager.AddObjectReference(ref, &object); |
| |
| int32_t pc = 2; |
| int32_t new_pc = pool_manager.Emit(&masm, |
| pc, |
| 0, /* no new code added */ |
| NULL, |
| NULL, |
| PoolManager<int32_t>::kNoBranchRequired); |
| USE(pc); |
| USE(new_pc); |
| VIXL_ASSERT(new_pc == AlignUp(pc, object_alignment) + object_size); |
| } |
| |
| static int32_t AddNBranches(PoolManager<int32_t> *pool_manager, |
| int32_t pc, |
| TestBranchObject *labels[], |
| int num_branches, |
| int branch_size, |
| int veneer_size, |
| int veneer_alignment, |
| int branch_range) { |
| for (int i = 0; i < num_branches; ++i) { |
| labels[i] = new TestBranchObject(veneer_size, veneer_alignment); |
| int32_t min_location = pc; |
| int32_t max_location = pc + branch_range; |
| ForwardReference<int32_t> *ref = |
| new ForwardReference<int32_t>(pc, |
| branch_size, |
| min_location, |
| max_location); |
| labels[i]->AddReference(ref); |
| // We have picked the object sizes so that we do not need to emit now. |
| VIXL_ASSERT(!pool_manager->MustEmit(pc, branch_size, ref, labels[i])); |
| pool_manager->AddObjectReference(ref, labels[i]); |
| pc += branch_size; |
| } |
| return pc; |
| } |
| |
| TEST(MustEmitNewReferenceDueToRange) { |
| const int kHeaderSize = 4; |
| const int kHeaderAlignment = 2; |
| const int kNumBranches = 550; |
| const int kBranchSize = 4; |
| const int kVeneerSize = 4; |
| const int kVeneerAlignment = 2; |
| const int kBranchRange = 1 * MBytes; |
| int32_t pc = 0; |
| |
| TestMacroAssembler masm; |
| TestBranchObject *labels[kNumBranches]; |
| PoolManager<int32_t> pool_manager(kHeaderSize, |
| kHeaderAlignment, |
| BUFFER_ALIGNMENT); |
| pc = AddNBranches(&pool_manager, |
| pc, |
| labels, |
| kNumBranches, |
| kBranchSize, |
| kVeneerSize, |
| kVeneerAlignment, |
| kBranchRange); |
| |
| // Increment PC to close to the checkpoint of the pools. |
| TestPoolManager test(&pool_manager); |
| pc = test.GetPoolCheckpoint() - 4; |
| VIXL_ASSERT(!pool_manager.MustEmit(pc)); |
| |
| // Now, attempt to add a reference that would make the problem impossible. |
| // We need to emit the pool immediately after this new instruction, and |
| // the current size of the pool is kVeneerSize * kNumBranches, so adding a |
| // short-range (smaller than the pool size) reference should trigger pool |
| // emission. |
| const int kPoolSize = kVeneerSize * kNumBranches + kHeaderSize; |
| |
| const int kNewObjectSize = 2; |
| TestObject new_object(kNewObjectSize, 1); |
| |
| ForwardReference<int32_t> temp_ref(pc, |
| kBranchSize, |
| pc, |
| pc + kPoolSize + kBranchSize - 1); |
| VIXL_ASSERT(pool_manager.MustEmit(pc, kBranchSize, &temp_ref, &new_object)); |
| |
| // Before actually emitting the pool, try a few different references to make |
| // sure that this works as expected. |
| { |
| // This reference has a large enough range, so should not force pool |
| // emission. |
| ForwardReference<int32_t> far_ref(pc, |
| kBranchSize, |
| pc, |
| pc + kPoolSize + kBranchSize); |
| VIXL_ASSERT(!pool_manager.MustEmit(pc, kBranchSize, &far_ref, &new_object)); |
| |
| // This reference had a large enough range but will be restricted by |
| // alignment so should force pool emission. |
| int alignment = 16; |
| VIXL_ASSERT((pc & (alignment - 1)) != 0); |
| ForwardReference<int32_t> aligned_ref(pc, |
| kBranchSize, |
| pc, |
| pc + kPoolSize + kBranchSize, |
| alignment); |
| VIXL_ASSERT( |
| pool_manager.MustEmit(pc, kBranchSize, &aligned_ref, &new_object)); |
| } |
| |
| // Emit the pool and check its size. |
| int32_t new_pc = |
| pool_manager.Emit(&masm, pc, kBranchSize, &temp_ref, &new_object); |
| VIXL_ASSERT(pc % kHeaderAlignment == 0); // No need for padding. |
| VIXL_ASSERT(new_pc == pc + kPoolSize); |
| pc = new_pc; |
| |
| // Add the new reference, safely. |
| ForwardReference<int32_t> *ref = |
| new ForwardReference<int32_t>(pc, 4 /*size*/, pc, pc + kBranchRange); |
| new_object.AddReference(ref); |
| pool_manager.AddObjectReference(ref, &new_object); |
| pc += 4; |
| |
| // Emit the pool again. |
| new_pc = pool_manager.Emit(&masm, pc); |
| VIXL_ASSERT(pc % kHeaderAlignment == 0); // No need for padding. |
| VIXL_ASSERT(new_pc == pc + kNewObjectSize + kHeaderSize); |
| pc = new_pc; |
| |
| // Finally, bind the labels. |
| for (int i = 0; i < kNumBranches; ++i) { |
| pc = pool_manager.Bind(&masm, labels[i], pc); |
| delete labels[i]; |
| } |
| } |
| |
| TEST(MustEmitNewReferenceDueToSizeOfObject) { |
| const int kHeaderSize = 4; |
| const int kHeaderAlignment = 2; |
| const int kNumBranches = 550; |
| const int kBranchSize = 4; |
| const int kVeneerSize = 4; |
| const int kVeneerAlignment = 2; |
| const int kBranchRange = 1 * MBytes; |
| int32_t pc = 0; |
| |
| TestMacroAssembler masm; |
| PoolManager<int32_t> pool_manager(kHeaderSize, |
| kHeaderAlignment, |
| BUFFER_ALIGNMENT); |
| TestBranchObject *labels[kNumBranches]; |
| pc = AddNBranches(&pool_manager, |
| pc, |
| labels, |
| kNumBranches, |
| kBranchSize, |
| kVeneerSize, |
| kVeneerAlignment, |
| kBranchRange); |
| |
| |
| // Increment PC to close to the checkpoint of the pools minus a known |
| // thershold. |
| const int kBigObjectSize = 1024; |
| TestPoolManager test(&pool_manager); |
| pc = test.GetPoolCheckpoint() - kBigObjectSize; |
| VIXL_ASSERT(!pool_manager.MustEmit(pc)); |
| |
| // Now, attempt to add a reference that would make the problem impossible. |
| // If we add a short-range (smaller than the pool size) reference with a |
| // large size (larger than the margin we have until pool emission), pool |
| // emission should be triggered. |
| const int kPoolSize = kVeneerSize * kNumBranches + kHeaderSize; |
| |
| TestObject new_object(kBigObjectSize, 1); |
| ForwardReference<int32_t> temp_ref(pc, kBranchSize, pc, pc + kPoolSize); |
| VIXL_ASSERT(pool_manager.MustEmit(pc, kBranchSize, &temp_ref, &new_object)); |
| |
| // Before actually emitting the pool, try a few different references to make |
| // sure that this works as expected. |
| { |
| // If the object is smaller, we can emit the reference. |
| TestObject smaller_object(kBigObjectSize - 4, 1); |
| ForwardReference<int32_t> temp_ref(pc, kBranchSize, pc, pc + kPoolSize); |
| VIXL_ASSERT( |
| !pool_manager.MustEmit(pc, kBranchSize, &temp_ref, &smaller_object)); |
| |
| // If the reference is going to be added after the current objects in the |
| // pool, we can still emit it. |
| ForwardReference<int32_t> far_ref(pc, kBranchSize, pc, pc + kBranchRange); |
| VIXL_ASSERT(!pool_manager.MustEmit(pc, kBranchSize, &far_ref, &new_object)); |
| } |
| |
| // Emit the pool and check its size. |
| int32_t new_pc = |
| pool_manager.Emit(&masm, pc, kBranchSize, &temp_ref, &new_object); |
| VIXL_ASSERT(pc % kHeaderAlignment == 0); // No need for padding. |
| VIXL_ASSERT(new_pc == pc + kPoolSize); |
| pc = new_pc; |
| |
| // Add the new reference, safely. |
| ForwardReference<int32_t> *ref = |
| new ForwardReference<int32_t>(pc, 4 /*size*/, pc, pc + kBranchRange); |
| new_object.AddReference(ref); |
| pool_manager.AddObjectReference(ref, &new_object); |
| pc += 4; |
| |
| // Emit the pool again. |
| new_pc = pool_manager.Emit(&masm, pc); |
| VIXL_ASSERT(pc % kHeaderAlignment == 0); // No need for padding. |
| VIXL_ASSERT(new_pc == pc + kBigObjectSize + kHeaderSize); |
| pc = new_pc; |
| |
| // Finally, bind the labels. |
| for (int i = 0; i < kNumBranches; ++i) { |
| pc = pool_manager.Bind(&masm, labels[i], pc); |
| delete labels[i]; |
| } |
| } |
| |
| template <typename ObjectType> |
| void ManagedLocationBaseTestHelper() { |
| TestMacroAssembler masm; |
| |
| PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 2 /*header_alignment*/, |
| BUFFER_ALIGNMENT); |
| ObjectType *object1 = new ObjectType(); |
| ObjectType *object2 = new ObjectType(); |
| ForwardReference<int32_t> *ref_obj1 = |
| new ForwardReference<int32_t>(0 /*location*/, 2 /*size*/, 0, 200); |
| object1->AddReference(ref_obj1); |
| ForwardReference<int32_t> *ref_obj2 = |
| new ForwardReference<int32_t>(8 /*location*/, 2 /*size*/, 8, 500); |
| object2->AddReference(ref_obj2); |
| |
| pool_manager.AddObjectReference(ref_obj1, object1); |
| pool_manager.AddObjectReference(ref_obj2, object2); |
| |
| pool_manager.Emit(&masm, 20); |
| } |
| |
| class TestObjectDeletedOnPlacement : public TestObject { |
| public: |
| TestObjectDeletedOnPlacement() : TestObject(4 /*size*/, 4 /*alignment*/) {} |
| // After passing ownership of this type of object to the pool manager, it is |
| // not safe to use it anymore. |
| virtual bool ShouldBeDeletedOnPlacementByPoolManager() const VIXL_OVERRIDE { |
| return true; |
| } |
| }; |
| |
| TEST(DeleteLocationBaseOnPlacement) { |
| ManagedLocationBaseTestHelper<TestObjectDeletedOnPlacement>(); |
| } |
| |
| class TestObjectDeletedOnPoolManagerDestruction : public TestObject { |
| public: |
| TestObjectDeletedOnPoolManagerDestruction() |
| : TestObject(4 /*size*/, 4 /*alignment*/) {} |
| // We can continue using this type of object after passing its ownership to |
| // the pool manager, as it will be deleted only when the pool manager is |
| // destroyed. |
| virtual bool ShouldBeDeletedOnPoolManagerDestruction() const VIXL_OVERRIDE { |
| return true; |
| } |
| }; |
| |
| |
| TEST(DeleteLocationBaseOnPoolManagerDestruction) { |
| ManagedLocationBaseTestHelper<TestObjectDeletedOnPoolManagerDestruction>(); |
| } |