Georgia Kouveli | 1cb7144 | 2017-01-30 13:35:28 +0000 | [diff] [blame] | 1 | // Copyright 2017, VIXL authors |
| 2 | // All rights reserved. |
| 3 | // |
| 4 | // Redistribution and use in source and binary forms, with or without |
| 5 | // modification, are permitted provided that the following conditions are met: |
| 6 | // |
| 7 | // * Redistributions of source code must retain the above copyright notice, |
| 8 | // this list of conditions and the following disclaimer. |
| 9 | // * Redistributions in binary form must reproduce the above copyright notice, |
| 10 | // this list of conditions and the following disclaimer in the documentation |
| 11 | // and/or other materials provided with the distribution. |
| 12 | // * Neither the name of ARM Limited nor the names of its contributors may be |
| 13 | // used to endorse or promote products derived from this software without |
| 14 | // specific prior written permission. |
| 15 | // |
| 16 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND |
| 17 | // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 18 | // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 19 | // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE |
| 20 | // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| 21 | // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| 22 | // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| 23 | // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
| 24 | // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 25 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 26 | |
| 27 | #include "test-pool-manager.h" |
| 28 | |
| 29 | #include <stdio.h> |
| 30 | |
Georgia Kouveli | 1cb7144 | 2017-01-30 13:35:28 +0000 | [diff] [blame] | 31 | #include "pool-manager-impl.h" |
Pierre Langlois | 1bce007 | 2017-06-06 17:58:58 +0100 | [diff] [blame] | 32 | #include "pool-manager.h" |
Georgia Kouveli | 1cb7144 | 2017-01-30 13:35:28 +0000 | [diff] [blame] | 33 | #include "test-runner.h" |
| 34 | |
| 35 | #define TEST(Name) TEST_(POOL_MANAGER_##Name) |
| 36 | |
| 37 | #define IF_VERBOSE(exp) \ |
| 38 | if (Test::verbose()) exp |
| 39 | |
| 40 | #define BUFFER_ALIGNMENT 16 |
| 41 | |
| 42 | using namespace vixl; |
| 43 | |
| 44 | static int Random() { return static_cast<int>(std::abs(mrand48())); } |
| 45 | |
| 46 | static int RandomObjectID(size_t num_objects) { return Random() % num_objects; } |
| 47 | |
| 48 | static int RandomObjectSize() { return 1 + Random() % 256; } |
| 49 | |
| 50 | static int RandomObjectAlignment(int size) { |
| 51 | const int limit = static_cast<int>(floor(log2(BUFFER_ALIGNMENT))); |
| 52 | int log2Size = static_cast<int>(floor(log2(size))); |
| 53 | // Restrict alignment due to buffer alignment. |
| 54 | log2Size = std::min(log2Size, limit); |
| 55 | return (1 << (Random() % (1 + log2Size))); |
| 56 | } |
| 57 | |
| 58 | // The size of the instruction. |
| 59 | static int RandomReferenceSize() { return (Random() % 2) ? 2 : 4; } |
| 60 | |
| 61 | // The alignment of an instruction is either 2 or 4. |
| 62 | static int RandomInstructionAlignment() { return (Random() % 2) ? 2 : 4; } |
| 63 | |
| 64 | static int32_t RandomMinOffset() { |
| 65 | const int N = 3; |
| 66 | static const int offsets[N] = {0, 2, 4}; |
| 67 | return offsets[Random() % N]; |
| 68 | } |
| 69 | |
| 70 | static int32_t RandomMaxOffset() { |
| 71 | const int N = 5; |
| 72 | static const int offsets[N] = {255, 1020, 1024, 4096, 16384}; |
| 73 | return offsets[Random() % N]; |
| 74 | } |
| 75 | |
| 76 | static int32_t RandomBranchMaxOffset() { |
| 77 | const int N = 10; |
| 78 | // The maximum offsets used for testing are taken from A32 and T32. |
| 79 | static const int offsets[N] = |
| 80 | {126, 254, 255, 1020, 1024, 2046, 4095, 1048574, 16777214, 33554428}; |
| 81 | return offsets[Random() % N]; |
| 82 | } |
| 83 | |
| 84 | static int RandomPCIncrement() { |
| 85 | // A multiple of two. |
| 86 | return 2 * (Random() % 4 + 1); |
| 87 | } |
| 88 | |
Georgia Kouveli | 8b57c86 | 2017-03-02 15:18:58 +0000 | [diff] [blame] | 89 | class TestObject : public LocationBase<int32_t> { |
Georgia Kouveli | 1cb7144 | 2017-01-30 13:35:28 +0000 | [diff] [blame] | 90 | public: |
| 91 | TestObject(int size, int alignment, int id = 0) |
Georgia Kouveli | 8b57c86 | 2017-03-02 15:18:58 +0000 | [diff] [blame] | 92 | : LocationBase(0 /*type*/, size, alignment), id_(id) {} |
Georgia Kouveli | 1cb7144 | 2017-01-30 13:35:28 +0000 | [diff] [blame] | 93 | |
| 94 | ~TestObject() VIXL_THROW_IN_NEGATIVE_TESTING_MODE(std::runtime_error) {} |
| 95 | |
| 96 | void EmitPoolObject(MacroAssemblerInterface *masm) VIXL_OVERRIDE { |
| 97 | USE(masm); |
| 98 | } |
| 99 | |
| 100 | bool ShouldDeletePoolObjectOnPlacement() const VIXL_OVERRIDE { return true; } |
| 101 | |
| 102 | // Update the references to this object. |
| 103 | void ResolveReferences(internal::AssemblerBase *assembler) VIXL_OVERRIDE { |
| 104 | int32_t location = GetLocation(); |
| 105 | USE(assembler); |
| 106 | for (std::vector<ForwardReference<int32_t> *>::iterator iter = |
| 107 | references_.begin(); |
| 108 | iter != references_.end();) { |
| 109 | ForwardReference<int32_t> *ref = *iter; |
| 110 | VIXL_ASSERT(ref->LocationIsEncodable(location)); |
| 111 | delete ref; |
| 112 | iter = references_.erase(iter); |
| 113 | } |
| 114 | IF_VERBOSE(printf("Placed object %d at location: 0x%x (%u)\n", |
| 115 | id_, |
| 116 | location, |
| 117 | location)); |
| 118 | } |
| 119 | |
| 120 | void AddReference(ForwardReference<int32_t> *ref) { |
| 121 | references_.push_back(ref); |
| 122 | } |
| 123 | |
| 124 | int GetID() { return id_; } |
| 125 | |
| 126 | static TestObject *CreateRandom(int id) { |
| 127 | int size = RandomObjectSize(); |
| 128 | int alignment = RandomObjectAlignment(size); |
| 129 | IF_VERBOSE(printf("Object %d -> size = %d, alignment = %d\n", |
| 130 | id, |
| 131 | size, |
| 132 | alignment)); |
| 133 | return new TestObject(size, alignment, id); |
| 134 | } |
| 135 | |
| 136 | private: |
| 137 | // Store pointers to ForwardReference objects - TestObject is responsible |
| 138 | // for deleting them. |
| 139 | std::vector<ForwardReference<int32_t> *> references_; |
| 140 | // Object id used for debugging. |
| 141 | int id_; |
| 142 | }; |
| 143 | |
Georgia Kouveli | 8b57c86 | 2017-03-02 15:18:58 +0000 | [diff] [blame] | 144 | class TestBranchObject : public LocationBase<int32_t> { |
Georgia Kouveli | 1cb7144 | 2017-01-30 13:35:28 +0000 | [diff] [blame] | 145 | public: |
| 146 | TestBranchObject(int size, int alignment, int id = 0) |
Georgia Kouveli | 8b57c86 | 2017-03-02 15:18:58 +0000 | [diff] [blame] | 147 | : LocationBase(1 /* type */, size, alignment), id_(id) {} |
Georgia Kouveli | 1cb7144 | 2017-01-30 13:35:28 +0000 | [diff] [blame] | 148 | |
| 149 | ~TestBranchObject() VIXL_THROW_IN_NEGATIVE_TESTING_MODE(std::runtime_error) {} |
| 150 | |
| 151 | |
| 152 | bool UsePoolObjectEmissionMargin() const VIXL_OVERRIDE { return true; } |
| 153 | int32_t GetPoolObjectEmissionMargin() const VIXL_OVERRIDE { |
| 154 | return 1 * KBytes; |
| 155 | } |
| 156 | |
| 157 | // Do nothing for now. |
| 158 | void EmitPoolObject(MacroAssemblerInterface *masm) VIXL_OVERRIDE { |
| 159 | USE(masm); |
| 160 | } |
| 161 | |
| 162 | bool ShouldDeletePoolObjectOnPlacement() const VIXL_OVERRIDE { return false; } |
| 163 | |
| 164 | virtual void UpdatePoolObject(PoolObject<int32_t> *object) VIXL_OVERRIDE { |
| 165 | // Reference from the last emitted veneer: |
| 166 | int32_t min = location_ + min_offset_; |
| 167 | int32_t max = location_ + max_offset_; |
| 168 | // The alignment that the new "veneer" requires of the label. |
| 169 | int reference_alignment = RandomInstructionAlignment(); |
| 170 | reference_alignment = |
| 171 | std::max(reference_alignment, GetPoolObjectAlignment()); |
| 172 | ForwardReference<int32_t> *ref = |
| 173 | new ForwardReference<int32_t>(location_, |
| 174 | 4 /*size*/, |
| 175 | min, |
| 176 | max, |
| 177 | reference_alignment); |
| 178 | AddReference(ref); |
| 179 | object->Update(min, max, reference_alignment); |
| 180 | } |
| 181 | |
| 182 | // Update the references to this object. |
| 183 | void ResolveReferences(internal::AssemblerBase *assembler) VIXL_OVERRIDE { |
| 184 | int32_t location = GetLocation(); |
| 185 | USE(assembler); |
| 186 | for (std::vector<ForwardReference<int32_t> *>::iterator iter = |
| 187 | references_.begin(); |
| 188 | iter != references_.end();) { |
| 189 | ForwardReference<int32_t> *ref = *iter; |
| 190 | VIXL_ASSERT(ref->LocationIsEncodable(location)); |
| 191 | delete ref; |
| 192 | iter = references_.erase(iter); |
| 193 | } |
| 194 | IF_VERBOSE(printf("Veneer %d placed at location: 0x%x (%u)\n", |
| 195 | id_, |
| 196 | location, |
| 197 | location)); |
| 198 | } |
| 199 | |
| 200 | void AddReference(ForwardReference<int32_t> *ref) { |
| 201 | references_.push_back(ref); |
| 202 | } |
| 203 | |
| 204 | virtual int GetMaxAlignment() const VIXL_OVERRIDE { |
| 205 | int max_alignment = GetPoolObjectAlignment(); |
| 206 | for (std::vector<ForwardReference<int32_t> *>::const_iterator iter = |
| 207 | references_.begin(); |
| 208 | iter != references_.end(); |
| 209 | ++iter) { |
| 210 | const ForwardReference<int32_t> *ref = *iter; |
| 211 | if (ref->GetAlignment() > max_alignment) |
| 212 | max_alignment = ref->GetAlignment(); |
| 213 | } |
| 214 | return max_alignment; |
| 215 | } |
| 216 | virtual int32_t GetMinLocation() const VIXL_OVERRIDE { |
| 217 | int32_t min_location = 0; |
| 218 | for (std::vector<ForwardReference<int32_t> *>::const_iterator iter = |
| 219 | references_.begin(); |
| 220 | iter != references_.end(); |
| 221 | ++iter) { |
| 222 | const ForwardReference<int32_t> *ref = *iter; |
| 223 | if (ref->GetMinLocation() > min_location) |
| 224 | min_location = ref->GetMinLocation(); |
| 225 | } |
| 226 | return min_location; |
| 227 | } |
| 228 | |
| 229 | int GetID() { return id_; } |
| 230 | |
| 231 | static TestBranchObject *CreateRandom(int id) { |
| 232 | int size = RandomReferenceSize(); |
| 233 | int alignment = size; |
| 234 | IF_VERBOSE(printf("Object %d -> size = %d, alignment = %d\n", |
| 235 | id, |
| 236 | size, |
| 237 | alignment)); |
| 238 | return new TestBranchObject(size, alignment, id); |
| 239 | } |
| 240 | |
| 241 | private: |
| 242 | // Store pointers to ForwardReference objects - TestBranchObject is |
| 243 | // responsible for deleting them. |
| 244 | std::vector<ForwardReference<int32_t> *> references_; |
| 245 | // Object id used for debugging. |
| 246 | int id_; |
| 247 | |
| 248 | // These are the min and max offsets of the type of branch used for the |
| 249 | // veneer. |
| 250 | static const int32_t min_offset_ = 0; |
| 251 | static const int32_t max_offset_ = 16 * 1024 * 1024; |
| 252 | }; |
| 253 | |
| 254 | // MacroAssembler implementation that does nothing but print in verbose mode. |
| 255 | class TestMacroAssembler : public MacroAssemblerInterface { |
| 256 | public: |
| 257 | TestMacroAssembler() : assembler_(128) {} |
| 258 | |
| 259 | void EmitPoolHeader() VIXL_OVERRIDE { |
| 260 | IF_VERBOSE(printf("[MASM] Emitting pool header.\n")); |
| 261 | } |
| 262 | void EmitPoolFooter() VIXL_OVERRIDE { |
| 263 | IF_VERBOSE(printf("[MASM] Emitting pool footer.\n")); |
| 264 | } |
| 265 | void EmitPaddingBytes(int n) VIXL_OVERRIDE { |
| 266 | IF_VERBOSE(printf("[MASM] Added %d bytes of padding.\n", n)); |
| 267 | } |
| 268 | void EmitNopBytes(int n) VIXL_OVERRIDE { |
| 269 | IF_VERBOSE(printf("[MASM] Added %d bytes of NOPs.\n", n)); |
| 270 | } |
| 271 | bool ArePoolsBlocked() const VIXL_OVERRIDE { return false; } |
| 272 | bool AllowMacroInstructions() const VIXL_OVERRIDE { return false; } |
| 273 | void SetAllowMacroInstructions(bool allow) VIXL_OVERRIDE { USE(allow); } |
| 274 | |
| 275 | void BlockPools() VIXL_OVERRIDE {} |
| 276 | void ReleasePools() VIXL_OVERRIDE {} |
| 277 | void EnsureEmitPoolsFor(size_t) VIXL_OVERRIDE {} |
| 278 | internal::AssemblerBase *AsAssemblerBase() VIXL_OVERRIDE { |
| 279 | return &assembler_; |
| 280 | } |
| 281 | |
| 282 | private: |
| 283 | internal::AssemblerBase assembler_; |
| 284 | }; |
| 285 | |
| 286 | // Used for debugging. |
| 287 | namespace vixl { |
| 288 | template <> |
| 289 | void PoolManager<int32_t>::DumpCurrentState(int32_t pc) const { |
| 290 | IF_VERBOSE( |
| 291 | printf("Number of objects: %d\n", static_cast<int>(objects_.size()))); |
| 292 | IF_VERBOSE(printf("Current pc = 0x%x (%d)\n", pc, pc)); |
| 293 | |
| 294 | for (int i = 0; i < static_cast<int>(objects_.size()); ++i) { |
| 295 | const PoolObject<int32_t> &object = objects_[i]; |
| 296 | IF_VERBOSE( |
| 297 | printf("Object %d -> size = %d, alignment = %d, range = (%d,%d)\n", |
| 298 | i, |
| 299 | object.label_base_->GetPoolObjectSizeInBytes(), |
| 300 | object.alignment_, |
| 301 | object.min_location_, |
| 302 | object.max_location_)); |
| 303 | } |
| 304 | } |
| 305 | } |
| 306 | |
| 307 | // Basic test - checks that emitting a very simple pool works. |
| 308 | TEST(Basic) { |
| 309 | TestMacroAssembler masm; |
| 310 | |
| 311 | PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 312 | 2 /*header_alignment*/, |
| 313 | BUFFER_ALIGNMENT); |
| 314 | TestObject object1(4 /*size*/, 4 /*alignment*/); |
| 315 | TestObject object2(128 /*size*/, 4 /*alignment*/); |
| 316 | ForwardReference<int32_t> *ref1_obj1 = |
| 317 | new ForwardReference<int32_t>(0 /*location*/, 2 /*size*/, 0, 200); |
| 318 | ForwardReference<int32_t> *ref2_obj1 = |
| 319 | new ForwardReference<int32_t>(2 /*location*/, 2 /*size*/, 2, 202); |
| 320 | ForwardReference<int32_t> *ref3_obj1 = |
| 321 | new ForwardReference<int32_t>(4 /*location*/, 2 /*size*/, 4, 204); |
| 322 | object1.AddReference(ref1_obj1); |
| 323 | object1.AddReference(ref2_obj1); |
| 324 | object1.AddReference(ref3_obj1); |
| 325 | ForwardReference<int32_t> *ref1_obj2 = |
| 326 | new ForwardReference<int32_t>(8 /*location*/, 2 /*size*/, 8, 500); |
| 327 | ForwardReference<int32_t> *ref2_obj2 = |
| 328 | new ForwardReference<int32_t>(12 /*location*/, 4 /*size*/, 12, 300); |
| 329 | ForwardReference<int32_t> *ref3_obj2 = |
| 330 | new ForwardReference<int32_t>(16 /*location*/, 4 /*size*/, 16, 400); |
| 331 | object2.AddReference(ref1_obj2); |
| 332 | object2.AddReference(ref2_obj2); |
| 333 | object2.AddReference(ref3_obj2); |
| 334 | |
| 335 | pool_manager.AddObjectReference(ref1_obj1, &object1); |
| 336 | pool_manager.AddObjectReference(ref2_obj1, &object1); |
| 337 | pool_manager.AddObjectReference(ref3_obj1, &object1); |
| 338 | pool_manager.AddObjectReference(ref1_obj2, &object2); |
| 339 | pool_manager.AddObjectReference(ref2_obj2, &object2); |
| 340 | pool_manager.AddObjectReference(ref3_obj2, &object2); |
| 341 | |
| 342 | pool_manager.Emit(&masm, 20); |
| 343 | } |
| 344 | |
| 345 | static ForwardReference<int32_t> *CreateReference(int id, |
| 346 | int32_t pc, |
| 347 | int size, |
| 348 | int32_t min_offset, |
| 349 | int32_t max_offset, |
| 350 | int alignment) { |
| 351 | IF_VERBOSE(printf( |
| 352 | "About to add a new reference to object %d with min location = %d, max " |
| 353 | "location = %d, alignment = %d, size = %d\n", |
| 354 | id, |
| 355 | min_offset + pc, |
| 356 | max_offset + pc, |
| 357 | alignment, |
| 358 | size)); |
| 359 | return new ForwardReference<int32_t>(pc, |
| 360 | size, |
| 361 | min_offset + pc, |
| 362 | max_offset + pc, |
| 363 | alignment); |
| 364 | } |
| 365 | |
| 366 | // Fuzz test that uses literal-like objects, that get deleted when they are |
| 367 | // placed. |
| 368 | TEST(FuzzObjectDeletedWhenPlaced) { |
| 369 | TestMacroAssembler masm; |
| 370 | PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 371 | 2 /*header_alignment*/, |
| 372 | BUFFER_ALIGNMENT); |
| 373 | |
| 374 | const int kObjectNum = 100; |
| 375 | std::vector<TestObject *> objects; |
| 376 | |
| 377 | // Create objects. |
| 378 | for (int i = 0; i < kObjectNum; ++i) { |
| 379 | objects.push_back(TestObject::CreateRandom(i)); |
| 380 | } |
| 381 | |
| 382 | int32_t pc = 0; |
| 383 | for (int i = 0; !objects.empty(); ++i) { |
| 384 | IF_VERBOSE(printf("PC = 0x%x (%d)\n", pc, pc)); |
| 385 | int32_t pc_increment = RandomPCIncrement(); |
| 386 | IF_VERBOSE(printf("Attempting to increment PC by %d\n", pc_increment)); |
| 387 | if (pool_manager.MustEmit(pc, pc_increment)) { |
| 388 | pc = pool_manager.Emit(&masm, pc, pc_increment); |
| 389 | } |
| 390 | pc += pc_increment; |
| 391 | // Pick an object, randomly. |
| 392 | TestObject *object = objects[RandomObjectID(objects.size())]; |
| 393 | int32_t min_offset = RandomMinOffset(); |
| 394 | int32_t max_offset = RandomMaxOffset(); |
| 395 | int32_t size = RandomReferenceSize(); |
| 396 | int32_t alignment = |
| 397 | RandomObjectAlignment(object->GetPoolObjectSizeInBytes()); |
| 398 | ForwardReference<int32_t> *ref = CreateReference(object->GetID(), |
| 399 | pc, |
| 400 | size, |
| 401 | min_offset, |
| 402 | max_offset, |
| 403 | alignment); |
| 404 | if (pool_manager.MustEmit(pc, size, ref, object)) { |
| 405 | pc = pool_manager.Emit(&masm, pc, size, ref, object); |
| 406 | delete ref; |
| 407 | // We must recreate the reference, the PC has changed, but only if |
| 408 | // it still is a forward reference. |
| 409 | if (!object->IsBound()) { |
| 410 | ref = CreateReference(object->GetID(), |
| 411 | pc, |
| 412 | size, |
| 413 | min_offset, |
| 414 | max_offset, |
| 415 | alignment); |
| 416 | } |
| 417 | } |
| 418 | IF_VERBOSE(printf("Incrementing PC by size of reference (%d).\n", size)); |
| 419 | pc += size; |
| 420 | // We only need to track the reference if it's a forward reference. |
| 421 | if (!object->IsBound()) { |
| 422 | object->AddReference(ref); |
| 423 | pool_manager.AddObjectReference(ref, object); |
| 424 | } |
| 425 | VIXL_ASSERT(!pool_manager.MustEmit(pc - 1)); |
| 426 | // Remove bound objects. |
| 427 | for (std::vector<TestObject *>::iterator iter = objects.begin(); |
| 428 | iter != objects.end();) { |
| 429 | TestObject *object = *iter; |
| 430 | if (object->IsBound()) { |
| 431 | delete object; |
| 432 | iter = objects.erase(iter); |
| 433 | } else { |
| 434 | ++iter; |
| 435 | } |
| 436 | } |
| 437 | } |
| 438 | |
| 439 | pool_manager.Emit(&masm, pc); |
| 440 | } |
| 441 | |
| 442 | // Fuzz test that uses veneer-like objects, that get updated when they are |
| 443 | // placed and get deleted when they are bound by the user. |
| 444 | TEST(FuzzObjectUpdatedWhenPlaced) { |
| 445 | TestMacroAssembler masm; |
| 446 | PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 447 | 2 /*header_alignment*/, |
| 448 | BUFFER_ALIGNMENT); |
| 449 | const int kObjectNum = 1000; |
| 450 | std::vector<TestBranchObject *> objects; |
| 451 | |
| 452 | // Create objects. |
| 453 | for (int i = 0; i < kObjectNum; ++i) { |
| 454 | objects.push_back(TestBranchObject::CreateRandom(i)); |
| 455 | } |
| 456 | |
| 457 | int32_t pc = 0; |
| 458 | for (int i = 0; !objects.empty(); ++i) { |
| 459 | IF_VERBOSE(printf("PC = 0x%x (%d)\n", pc, pc)); |
| 460 | |
| 461 | int32_t pc_increment = RandomPCIncrement(); |
| 462 | IF_VERBOSE(printf("Attempting to increment PC by %d\n", pc_increment)); |
| 463 | |
| 464 | if (pool_manager.MustEmit(pc, pc_increment)) { |
| 465 | pc = pool_manager.Emit(&masm, pc, pc_increment); |
| 466 | } |
| 467 | pc += pc_increment; |
| 468 | |
| 469 | // Pick a random object. |
| 470 | TestBranchObject *object = objects[RandomObjectID(objects.size())]; |
| 471 | int32_t min_offset = RandomMinOffset(); |
| 472 | int32_t max_offset = RandomBranchMaxOffset(); |
| 473 | int32_t size = RandomReferenceSize(); |
| 474 | int32_t alignment = |
| 475 | RandomObjectAlignment(object->GetPoolObjectSizeInBytes()); |
| 476 | ForwardReference<int32_t> *ref = CreateReference(object->GetID(), |
| 477 | pc, |
| 478 | size, |
| 479 | min_offset, |
| 480 | max_offset, |
| 481 | alignment); |
| 482 | if (pool_manager.MustEmit(pc, size, ref, object)) { |
| 483 | pc = pool_manager.Emit(&masm, pc, size); |
| 484 | delete ref; |
| 485 | // We must recreate the reference, the PC has changed. |
| 486 | ref = CreateReference(object->GetID(), |
| 487 | pc, |
| 488 | size, |
| 489 | min_offset, |
| 490 | max_offset, |
| 491 | alignment); |
| 492 | } |
| 493 | IF_VERBOSE(printf("Incrementing PC by size of reference (%d).\n", size)); |
| 494 | pc += size; |
| 495 | object->AddReference(ref); |
| 496 | pool_manager.AddObjectReference(ref, object); |
| 497 | VIXL_ASSERT(!pool_manager.MustEmit(pc - 1)); |
| 498 | |
| 499 | // Pick another random label to bind. |
| 500 | const int kProbabilityToBind = 20; |
| 501 | if ((Random() % 100) < kProbabilityToBind) { |
| 502 | TestBranchObject *object = objects[RandomObjectID(objects.size())]; |
| 503 | // Binding can cause the pool emission, so check if we need to emit |
| 504 | // the pools. The actual backends will know the max alignment we |
| 505 | // might need here, so can simplify the check (won't need to check |
| 506 | // the object references). |
| 507 | int max_padding = object->GetMaxAlignment() - 1; |
| 508 | if (pool_manager.MustEmit(pc, max_padding)) { |
| 509 | pc = pool_manager.Emit(&masm, pc, max_padding); |
| 510 | } |
| 511 | pc = pool_manager.Bind(&masm, object, pc); |
| 512 | } |
| 513 | |
| 514 | // Remove bound objects. |
| 515 | for (std::vector<TestBranchObject *>::iterator iter = objects.begin(); |
| 516 | iter != objects.end();) { |
| 517 | TestBranchObject *object = *iter; |
| 518 | if (object->IsBound()) { |
| 519 | delete object; |
| 520 | iter = objects.erase(iter); |
| 521 | } else { |
| 522 | ++iter; |
| 523 | } |
| 524 | } |
| 525 | } |
| 526 | |
| 527 | pool_manager.Emit(&masm, pc); |
| 528 | } |
| 529 | |
| 530 | // Test that binding an unused label works. |
| 531 | TEST(BindUnusedLabel) { |
| 532 | TestMacroAssembler masm; |
| 533 | |
| 534 | PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 535 | 2 /*header_alignment*/, |
| 536 | BUFFER_ALIGNMENT); |
| 537 | TestBranchObject *object = new TestBranchObject(4 /*size*/, 4 /*alignment*/); |
| 538 | int32_t pc = 0; |
| 539 | pool_manager.Bind(&masm, object, pc); |
| 540 | delete object; |
| 541 | } |
| 542 | |
| 543 | // Test that binding a label adds necessary padding. |
| 544 | TEST(BindLabelNeedsPadding) { |
| 545 | TestMacroAssembler masm; |
| 546 | |
| 547 | PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 548 | 2 /*header_alignment*/, |
| 549 | BUFFER_ALIGNMENT); |
| 550 | |
| 551 | // Label that needs padding because of the minimum location of the reference. |
| 552 | TestBranchObject *object = new TestBranchObject(4 /*size*/, 2 /*alignment*/); |
| 553 | ForwardReference<int32_t> *ref = |
| 554 | new ForwardReference<int32_t>(0 /*location*/, |
| 555 | 2 /*size*/, |
| 556 | 4 /*min_location*/, |
| 557 | 500 /*max_location*/); |
| 558 | object->AddReference(ref); |
| 559 | pool_manager.AddObjectReference(ref, object); |
| 560 | int32_t pc = 2; |
| 561 | pc = pool_manager.Bind(&masm, object, pc); |
| 562 | VIXL_ASSERT(pc == 4); |
| 563 | delete object; |
| 564 | |
| 565 | // Label that needs padding because of the alignment of the object. |
| 566 | object = new TestBranchObject(4 /*size*/, 4 /*alignment*/); |
| 567 | ref = new ForwardReference<int32_t>(0 /*location*/, |
| 568 | 2 /*size*/, |
| 569 | 0 /*min_location*/, |
| 570 | 500 /*max_location*/); |
| 571 | object->AddReference(ref); |
| 572 | pool_manager.AddObjectReference(ref, object); |
| 573 | |
| 574 | pc = 2; |
| 575 | pc = pool_manager.Bind(&masm, object, pc); |
| 576 | VIXL_ASSERT(pc == 4); |
| 577 | delete object; |
| 578 | |
| 579 | // Label that needs padding because of the alignment of the reference. |
| 580 | object = new TestBranchObject(4 /*size*/, 1 /*alignment*/); |
| 581 | ref = new ForwardReference<int32_t>(0 /*location*/, |
| 582 | 2 /*size*/, |
| 583 | 0 /*min_location*/, |
| 584 | 500 /*max_location*/, |
| 585 | 4 /*alignment*/); |
| 586 | object->AddReference(ref); |
| 587 | pool_manager.AddObjectReference(ref, object); |
| 588 | |
| 589 | pc = 2; |
| 590 | pc = pool_manager.Bind(&masm, object, pc); |
| 591 | VIXL_ASSERT(pc == 4); |
| 592 | delete object; |
| 593 | } |
| 594 | |
| 595 | // This test checks that when we omit the pool header, we insert any padding |
| 596 | // needed in order to meet the minimum location of the first object. |
| 597 | TEST(PoolWithoutHeaderMinLocation) { |
| 598 | TestMacroAssembler masm; |
| 599 | |
| 600 | PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 601 | 2 /*header_alignment*/, |
| 602 | BUFFER_ALIGNMENT); |
| 603 | int object_size = 4; |
| 604 | int object_alignment = 1; // Do not restrict alignment for this test. |
| 605 | int min_location = 4; // We emit the pool at location 2, so need padding. |
| 606 | int max_location = 500; |
| 607 | TestObject object(object_size, object_alignment); |
| 608 | ForwardReference<int32_t> *ref = new ForwardReference<int32_t>(0 /*location*/, |
| 609 | 2 /*size*/, |
| 610 | min_location, |
| 611 | max_location); |
| 612 | object.AddReference(ref); |
| 613 | pool_manager.AddObjectReference(ref, &object); |
| 614 | |
| 615 | int32_t new_pc = pool_manager.Emit(&masm, |
| 616 | 2, |
| 617 | 0, /* no new code added */ |
| 618 | NULL, |
| 619 | NULL, |
| 620 | PoolManager<int32_t>::kNoBranchRequired); |
| 621 | USE(new_pc); |
| 622 | VIXL_ASSERT(new_pc == min_location + object_size); |
| 623 | } |
| 624 | |
| 625 | // This test checks that when we omit the pool header, we insert any padding |
| 626 | // needed in order to meet the alignment of the first object. |
| 627 | TEST(PoolWithoutHeaderAlignment) { |
| 628 | TestMacroAssembler masm; |
| 629 | |
| 630 | PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 631 | 2 /*header_alignment*/, |
| 632 | BUFFER_ALIGNMENT); |
| 633 | int object_size = 4; |
| 634 | int object_alignment = 4; // We emit the pool at location 2, so need padding. |
| 635 | int min_location = 0; // Do not restrict this for this test. |
| 636 | int max_location = 500; |
| 637 | TestObject object(object_size, object_alignment); |
| 638 | ForwardReference<int32_t> *ref = new ForwardReference<int32_t>(0 /*location*/, |
| 639 | 2 /*size*/, |
| 640 | min_location, |
| 641 | max_location); |
| 642 | object.AddReference(ref); |
| 643 | pool_manager.AddObjectReference(ref, &object); |
| 644 | |
| 645 | int32_t pc = 2; |
| 646 | int32_t new_pc = pool_manager.Emit(&masm, |
| 647 | pc, |
| 648 | 0, /* no new code added */ |
| 649 | NULL, |
| 650 | NULL, |
| 651 | PoolManager<int32_t>::kNoBranchRequired); |
| 652 | USE(pc); |
| 653 | USE(new_pc); |
| 654 | VIXL_ASSERT(new_pc == AlignUp(pc, object_alignment) + object_size); |
| 655 | } |
| 656 | |
| 657 | static int32_t AddNBranches(PoolManager<int32_t> *pool_manager, |
| 658 | int32_t pc, |
| 659 | TestBranchObject *labels[], |
| 660 | int num_branches, |
| 661 | int branch_size, |
| 662 | int veneer_size, |
| 663 | int veneer_alignment, |
| 664 | int branch_range) { |
| 665 | for (int i = 0; i < num_branches; ++i) { |
| 666 | labels[i] = new TestBranchObject(veneer_size, veneer_alignment); |
| 667 | int32_t min_location = pc; |
| 668 | int32_t max_location = pc + branch_range; |
| 669 | ForwardReference<int32_t> *ref = |
| 670 | new ForwardReference<int32_t>(pc, |
| 671 | branch_size, |
| 672 | min_location, |
| 673 | max_location); |
| 674 | labels[i]->AddReference(ref); |
| 675 | // We have picked the object sizes so that we do not need to emit now. |
| 676 | VIXL_ASSERT(!pool_manager->MustEmit(pc, branch_size, ref, labels[i])); |
| 677 | pool_manager->AddObjectReference(ref, labels[i]); |
| 678 | pc += branch_size; |
| 679 | } |
| 680 | return pc; |
| 681 | } |
| 682 | |
| 683 | TEST(MustEmitNewReferenceDueToRange) { |
| 684 | const int kHeaderSize = 4; |
| 685 | const int kHeaderAlignment = 2; |
| 686 | const int kNumBranches = 550; |
| 687 | const int kBranchSize = 4; |
| 688 | const int kVeneerSize = 4; |
| 689 | const int kVeneerAlignment = 2; |
| 690 | const int kBranchRange = 1 * MBytes; |
| 691 | int32_t pc = 0; |
| 692 | |
| 693 | TestMacroAssembler masm; |
| 694 | TestBranchObject *labels[kNumBranches]; |
| 695 | PoolManager<int32_t> pool_manager(kHeaderSize, |
| 696 | kHeaderAlignment, |
| 697 | BUFFER_ALIGNMENT); |
| 698 | pc = AddNBranches(&pool_manager, |
| 699 | pc, |
| 700 | labels, |
| 701 | kNumBranches, |
| 702 | kBranchSize, |
| 703 | kVeneerSize, |
| 704 | kVeneerAlignment, |
| 705 | kBranchRange); |
| 706 | |
| 707 | // Increment PC to close to the checkpoint of the pools. |
| 708 | TestPoolManager test(&pool_manager); |
| 709 | pc = test.GetPoolCheckpoint() - 4; |
| 710 | VIXL_ASSERT(!pool_manager.MustEmit(pc)); |
| 711 | |
| 712 | // Now, attempt to add a reference that would make the problem impossible. |
| 713 | // We need to emit the pool immediately after this new instruction, and |
| 714 | // the current size of the pool is kVeneerSize * kNumBranches, so adding a |
| 715 | // short-range (smaller than the pool size) reference should trigger pool |
| 716 | // emission. |
| 717 | const int kPoolSize = kVeneerSize * kNumBranches + kHeaderSize; |
| 718 | |
| 719 | const int kNewObjectSize = 2; |
| 720 | TestObject new_object(kNewObjectSize, 1); |
| 721 | |
| 722 | ForwardReference<int32_t> temp_ref(pc, |
| 723 | kBranchSize, |
| 724 | pc, |
| 725 | pc + kPoolSize + kBranchSize - 1); |
| 726 | VIXL_ASSERT(pool_manager.MustEmit(pc, kBranchSize, &temp_ref, &new_object)); |
| 727 | |
| 728 | // Before actually emitting the pool, try a few different references to make |
| 729 | // sure that this works as expected. |
| 730 | { |
| 731 | // This reference has a large enough range, so should not force pool |
| 732 | // emission. |
| 733 | ForwardReference<int32_t> far_ref(pc, |
| 734 | kBranchSize, |
| 735 | pc, |
| 736 | pc + kPoolSize + kBranchSize); |
| 737 | VIXL_ASSERT(!pool_manager.MustEmit(pc, kBranchSize, &far_ref, &new_object)); |
| 738 | |
| 739 | // This reference had a large enough range but will be restricted by |
| 740 | // alignment so should force pool emission. |
| 741 | int alignment = 16; |
| 742 | VIXL_ASSERT((pc & (alignment - 1)) != 0); |
| 743 | ForwardReference<int32_t> aligned_ref(pc, |
| 744 | kBranchSize, |
| 745 | pc, |
| 746 | pc + kPoolSize + kBranchSize, |
| 747 | alignment); |
| 748 | VIXL_ASSERT( |
| 749 | pool_manager.MustEmit(pc, kBranchSize, &aligned_ref, &new_object)); |
| 750 | } |
| 751 | |
| 752 | // Emit the pool and check its size. |
| 753 | int32_t new_pc = |
| 754 | pool_manager.Emit(&masm, pc, kBranchSize, &temp_ref, &new_object); |
| 755 | VIXL_ASSERT(pc % kHeaderAlignment == 0); // No need for padding. |
| 756 | VIXL_ASSERT(new_pc == pc + kPoolSize); |
| 757 | pc = new_pc; |
| 758 | |
| 759 | // Add the new reference, safely. |
| 760 | ForwardReference<int32_t> *ref = |
| 761 | new ForwardReference<int32_t>(pc, 4 /*size*/, pc, pc + kBranchRange); |
| 762 | new_object.AddReference(ref); |
| 763 | pool_manager.AddObjectReference(ref, &new_object); |
| 764 | pc += 4; |
| 765 | |
| 766 | // Emit the pool again. |
| 767 | new_pc = pool_manager.Emit(&masm, pc); |
| 768 | VIXL_ASSERT(pc % kHeaderAlignment == 0); // No need for padding. |
| 769 | VIXL_ASSERT(new_pc == pc + kNewObjectSize + kHeaderSize); |
| 770 | pc = new_pc; |
| 771 | |
| 772 | // Finally, bind the labels. |
| 773 | for (int i = 0; i < kNumBranches; ++i) { |
| 774 | pc = pool_manager.Bind(&masm, labels[i], pc); |
| 775 | delete labels[i]; |
| 776 | } |
| 777 | } |
| 778 | |
| 779 | TEST(MustEmitNewReferenceDueToSizeOfObject) { |
| 780 | const int kHeaderSize = 4; |
| 781 | const int kHeaderAlignment = 2; |
| 782 | const int kNumBranches = 550; |
| 783 | const int kBranchSize = 4; |
| 784 | const int kVeneerSize = 4; |
| 785 | const int kVeneerAlignment = 2; |
| 786 | const int kBranchRange = 1 * MBytes; |
| 787 | int32_t pc = 0; |
| 788 | |
| 789 | TestMacroAssembler masm; |
| 790 | PoolManager<int32_t> pool_manager(kHeaderSize, |
| 791 | kHeaderAlignment, |
| 792 | BUFFER_ALIGNMENT); |
| 793 | TestBranchObject *labels[kNumBranches]; |
| 794 | pc = AddNBranches(&pool_manager, |
| 795 | pc, |
| 796 | labels, |
| 797 | kNumBranches, |
| 798 | kBranchSize, |
| 799 | kVeneerSize, |
| 800 | kVeneerAlignment, |
| 801 | kBranchRange); |
| 802 | |
| 803 | |
| 804 | // Increment PC to close to the checkpoint of the pools minus a known |
| 805 | // thershold. |
| 806 | const int kBigObjectSize = 1024; |
| 807 | TestPoolManager test(&pool_manager); |
| 808 | pc = test.GetPoolCheckpoint() - kBigObjectSize; |
| 809 | VIXL_ASSERT(!pool_manager.MustEmit(pc)); |
| 810 | |
| 811 | // Now, attempt to add a reference that would make the problem impossible. |
| 812 | // If we add a short-range (smaller than the pool size) reference with a |
| 813 | // large size (larger than the margin we have until pool emission), pool |
| 814 | // emission should be triggered. |
| 815 | const int kPoolSize = kVeneerSize * kNumBranches + kHeaderSize; |
| 816 | |
| 817 | TestObject new_object(kBigObjectSize, 1); |
| 818 | ForwardReference<int32_t> temp_ref(pc, kBranchSize, pc, pc + kPoolSize); |
| 819 | VIXL_ASSERT(pool_manager.MustEmit(pc, kBranchSize, &temp_ref, &new_object)); |
| 820 | |
| 821 | // Before actually emitting the pool, try a few different references to make |
| 822 | // sure that this works as expected. |
| 823 | { |
| 824 | // If the object is smaller, we can emit the reference. |
| 825 | TestObject smaller_object(kBigObjectSize - 4, 1); |
| 826 | ForwardReference<int32_t> temp_ref(pc, kBranchSize, pc, pc + kPoolSize); |
| 827 | VIXL_ASSERT( |
| 828 | !pool_manager.MustEmit(pc, kBranchSize, &temp_ref, &smaller_object)); |
| 829 | |
| 830 | // If the reference is going to be added after the current objects in the |
| 831 | // pool, we can still emit it. |
| 832 | ForwardReference<int32_t> far_ref(pc, kBranchSize, pc, pc + kBranchRange); |
| 833 | VIXL_ASSERT(!pool_manager.MustEmit(pc, kBranchSize, &far_ref, &new_object)); |
| 834 | } |
| 835 | |
| 836 | // Emit the pool and check its size. |
| 837 | int32_t new_pc = |
| 838 | pool_manager.Emit(&masm, pc, kBranchSize, &temp_ref, &new_object); |
| 839 | VIXL_ASSERT(pc % kHeaderAlignment == 0); // No need for padding. |
| 840 | VIXL_ASSERT(new_pc == pc + kPoolSize); |
| 841 | pc = new_pc; |
| 842 | |
| 843 | // Add the new reference, safely. |
| 844 | ForwardReference<int32_t> *ref = |
| 845 | new ForwardReference<int32_t>(pc, 4 /*size*/, pc, pc + kBranchRange); |
| 846 | new_object.AddReference(ref); |
| 847 | pool_manager.AddObjectReference(ref, &new_object); |
| 848 | pc += 4; |
| 849 | |
| 850 | // Emit the pool again. |
| 851 | new_pc = pool_manager.Emit(&masm, pc); |
| 852 | VIXL_ASSERT(pc % kHeaderAlignment == 0); // No need for padding. |
| 853 | VIXL_ASSERT(new_pc == pc + kBigObjectSize + kHeaderSize); |
| 854 | pc = new_pc; |
| 855 | |
| 856 | // Finally, bind the labels. |
| 857 | for (int i = 0; i < kNumBranches; ++i) { |
| 858 | pc = pool_manager.Bind(&masm, labels[i], pc); |
| 859 | delete labels[i]; |
| 860 | } |
| 861 | } |
Georgia Kouveli | 4443d2c | 2017-05-09 18:14:15 +0100 | [diff] [blame] | 862 | |
| 863 | template <typename ObjectType> |
Georgia Kouveli | 8b57c86 | 2017-03-02 15:18:58 +0000 | [diff] [blame] | 864 | void ManagedLocationBaseTestHelper() { |
Georgia Kouveli | 4443d2c | 2017-05-09 18:14:15 +0100 | [diff] [blame] | 865 | TestMacroAssembler masm; |
| 866 | |
| 867 | PoolManager<int32_t> pool_manager(4 /*header_size*/, |
| 868 | 2 /*header_alignment*/, |
| 869 | BUFFER_ALIGNMENT); |
| 870 | ObjectType *object1 = new ObjectType(); |
| 871 | ObjectType *object2 = new ObjectType(); |
| 872 | ForwardReference<int32_t> *ref_obj1 = |
| 873 | new ForwardReference<int32_t>(0 /*location*/, 2 /*size*/, 0, 200); |
| 874 | object1->AddReference(ref_obj1); |
| 875 | ForwardReference<int32_t> *ref_obj2 = |
| 876 | new ForwardReference<int32_t>(8 /*location*/, 2 /*size*/, 8, 500); |
| 877 | object2->AddReference(ref_obj2); |
| 878 | |
| 879 | pool_manager.AddObjectReference(ref_obj1, object1); |
| 880 | pool_manager.AddObjectReference(ref_obj2, object2); |
| 881 | |
| 882 | pool_manager.Emit(&masm, 20); |
| 883 | } |
| 884 | |
| 885 | class TestObjectDeletedOnPlacement : public TestObject { |
| 886 | public: |
| 887 | TestObjectDeletedOnPlacement() : TestObject(4 /*size*/, 4 /*alignment*/) {} |
| 888 | // After passing ownership of this type of object to the pool manager, it is |
| 889 | // not safe to use it anymore. |
| 890 | virtual bool ShouldBeDeletedOnPlacementByPoolManager() const VIXL_OVERRIDE { |
| 891 | return true; |
| 892 | } |
| 893 | }; |
| 894 | |
Georgia Kouveli | 8b57c86 | 2017-03-02 15:18:58 +0000 | [diff] [blame] | 895 | TEST(DeleteLocationBaseOnPlacement) { |
| 896 | ManagedLocationBaseTestHelper<TestObjectDeletedOnPlacement>(); |
Georgia Kouveli | 4443d2c | 2017-05-09 18:14:15 +0100 | [diff] [blame] | 897 | } |
| 898 | |
| 899 | class TestObjectDeletedOnPoolManagerDestruction : public TestObject { |
| 900 | public: |
| 901 | TestObjectDeletedOnPoolManagerDestruction() |
| 902 | : TestObject(4 /*size*/, 4 /*alignment*/) {} |
| 903 | // We can continue using this type of object after passing its ownership to |
| 904 | // the pool manager, as it will be deleted only when the pool manager is |
| 905 | // destroyed. |
| 906 | virtual bool ShouldBeDeletedOnPoolManagerDestruction() const VIXL_OVERRIDE { |
| 907 | return true; |
| 908 | } |
| 909 | }; |
| 910 | |
| 911 | |
Georgia Kouveli | 8b57c86 | 2017-03-02 15:18:58 +0000 | [diff] [blame] | 912 | TEST(DeleteLocationBaseOnPoolManagerDestruction) { |
| 913 | ManagedLocationBaseTestHelper<TestObjectDeletedOnPoolManagerDestruction>(); |
Georgia Kouveli | 4443d2c | 2017-05-09 18:14:15 +0100 | [diff] [blame] | 914 | } |