You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
910 lines
33 KiB
910 lines
33 KiB
// 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);
|
|
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];
|
|
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>();
|
|
}
|