/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include "jit/shared/IonAssemblerBufferWithConstantPools.h" #include "jsapi-tests/tests.h" #include "vm/JSAtom.h" // Tests for classes in: // // jit/shared/IonAssemblerBuffer.h // jit/shared/IonAssemblerBufferWithConstantPools.h // // Classes in js::jit tested: // // BufferOffset // BufferSlice (implicitly) // AssemblerBuffer // // BranchDeadlineSet // Pool (implicitly) // AssemblerBufferWithConstantPools // BEGIN_TEST(testAssemblerBuffer_BufferOffset) { using js::jit::BufferOffset; BufferOffset off1; BufferOffset off2(10); CHECK(!off1.assigned()); CHECK(off2.assigned()); CHECK_EQUAL(off2.getOffset(), 10); off1 = off2; CHECK(off1.assigned()); CHECK_EQUAL(off1.getOffset(), 10); return true; } END_TEST(testAssemblerBuffer_BufferOffset) BEGIN_TEST(testAssemblerBuffer_AssemblerBuffer) { using js::jit::BufferOffset; typedef js::jit::AssemblerBuffer<5 * sizeof(uint32_t), uint32_t> AsmBuf; AsmBuf ab; CHECK(ab.isAligned(16)); CHECK_EQUAL(ab.size(), 0u); CHECK_EQUAL(ab.nextOffset().getOffset(), 0); CHECK(!ab.oom()); BufferOffset off1 = ab.putInt(1000017); CHECK_EQUAL(off1.getOffset(), 0); CHECK_EQUAL(ab.size(), 4u); CHECK_EQUAL(ab.nextOffset().getOffset(), 4); CHECK(!ab.isAligned(16)); CHECK(ab.isAligned(4)); CHECK(ab.isAligned(1)); CHECK_EQUAL(*ab.getInst(off1), 1000017u); BufferOffset off2 = ab.putInt(1000018); CHECK_EQUAL(off2.getOffset(), 4); BufferOffset off3 = ab.putInt(1000019); CHECK_EQUAL(off3.getOffset(), 8); BufferOffset off4 = ab.putInt(1000020); CHECK_EQUAL(off4.getOffset(), 12); CHECK_EQUAL(ab.size(), 16u); CHECK_EQUAL(ab.nextOffset().getOffset(), 16); // Last one in the slice. BufferOffset off5 = ab.putInt(1000021); CHECK_EQUAL(off5.getOffset(), 16); CHECK_EQUAL(ab.size(), 20u); CHECK_EQUAL(ab.nextOffset().getOffset(), 20); BufferOffset off6 = ab.putInt(1000022); CHECK_EQUAL(off6.getOffset(), 20); CHECK_EQUAL(ab.size(), 24u); CHECK_EQUAL(ab.nextOffset().getOffset(), 24); // Reference previous slice. Excercise the finger. CHECK_EQUAL(*ab.getInst(off1), 1000017u); CHECK_EQUAL(*ab.getInst(off6), 1000022u); CHECK_EQUAL(*ab.getInst(off1), 1000017u); CHECK_EQUAL(*ab.getInst(off5), 1000021u); // Too much data for one slice. const uint32_t fixdata[] = {2000036, 2000037, 2000038, 2000039, 2000040, 2000041}; // Split payload across multiple slices. CHECK_EQUAL(ab.nextOffset().getOffset(), 24); BufferOffset good1 = ab.putBytesLarge(sizeof(fixdata), fixdata); CHECK_EQUAL(good1.getOffset(), 24); CHECK_EQUAL(ab.nextOffset().getOffset(), 48); CHECK_EQUAL(*ab.getInst(good1), 2000036u); CHECK_EQUAL(*ab.getInst(BufferOffset(32)), 2000038u); CHECK_EQUAL(*ab.getInst(BufferOffset(36)), 2000039u); CHECK_EQUAL(*ab.getInst(BufferOffset(40)), 2000040u); CHECK_EQUAL(*ab.getInst(BufferOffset(44)), 2000041u); return true; } END_TEST(testAssemblerBuffer_AssemblerBuffer) BEGIN_TEST(testAssemblerBuffer_BranchDeadlineSet) { typedef js::jit::BranchDeadlineSet<3> DLSet; using js::jit::BufferOffset; js::LifoAlloc alloc(1024); DLSet dls(alloc); CHECK(dls.empty()); CHECK(alloc.isEmpty()); // Constructor must be infallible. CHECK_EQUAL(dls.size(), 0u); CHECK_EQUAL(dls.maxRangeSize(), 0u); // Removing non-existant deadline is OK. dls.removeDeadline(1, BufferOffset(7)); // Add deadlines in increasing order as intended. This is optimal. dls.addDeadline(1, BufferOffset(10)); CHECK(!dls.empty()); CHECK_EQUAL(dls.size(), 1u); CHECK_EQUAL(dls.maxRangeSize(), 1u); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); CHECK_EQUAL(dls.earliestDeadlineRange(), 1u); // Removing non-existant deadline is OK. dls.removeDeadline(1, BufferOffset(7)); dls.removeDeadline(1, BufferOffset(17)); dls.removeDeadline(0, BufferOffset(10)); CHECK_EQUAL(dls.size(), 1u); CHECK_EQUAL(dls.maxRangeSize(), 1u); // Two identical deadlines for different ranges. dls.addDeadline(2, BufferOffset(10)); CHECK(!dls.empty()); CHECK_EQUAL(dls.size(), 2u); CHECK_EQUAL(dls.maxRangeSize(), 1u); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); // It doesn't matter which range earliestDeadlineRange() reports first, // but it must report both. if (dls.earliestDeadlineRange() == 1) { dls.removeDeadline(1, BufferOffset(10)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); CHECK_EQUAL(dls.earliestDeadlineRange(), 2u); } else { CHECK_EQUAL(dls.earliestDeadlineRange(), 2u); dls.removeDeadline(2, BufferOffset(10)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); CHECK_EQUAL(dls.earliestDeadlineRange(), 1u); } // Add deadline which is the front of range 0, but not the global earliest. dls.addDeadline(0, BufferOffset(20)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); CHECK(dls.earliestDeadlineRange() > 0); // Non-optimal add to front of single-entry range 0. dls.addDeadline(0, BufferOffset(15)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); CHECK(dls.earliestDeadlineRange() > 0); // Append to 2-entry range 0. dls.addDeadline(0, BufferOffset(30)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); CHECK(dls.earliestDeadlineRange() > 0); // Add penultimate entry. dls.addDeadline(0, BufferOffset(25)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); CHECK(dls.earliestDeadlineRange() > 0); // Prepend, stealing earliest from other range. dls.addDeadline(0, BufferOffset(5)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 5); CHECK_EQUAL(dls.earliestDeadlineRange(), 0u); // Remove central element. dls.removeDeadline(0, BufferOffset(20)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 5); CHECK_EQUAL(dls.earliestDeadlineRange(), 0u); // Remove front, giving back the lead. dls.removeDeadline(0, BufferOffset(5)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 10); CHECK(dls.earliestDeadlineRange() > 0); // Remove front, giving back earliest to range 0. dls.removeDeadline(dls.earliestDeadlineRange(), BufferOffset(10)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 15); CHECK_EQUAL(dls.earliestDeadlineRange(), 0u); // Remove tail. dls.removeDeadline(0, BufferOffset(30)); CHECK_EQUAL(dls.earliestDeadline().getOffset(), 15); CHECK_EQUAL(dls.earliestDeadlineRange(), 0u); // Now range 0 = [15, 25]. CHECK_EQUAL(dls.size(), 2u); dls.removeDeadline(0, BufferOffset(25)); dls.removeDeadline(0, BufferOffset(15)); CHECK(dls.empty()); return true; } END_TEST(testAssemblerBuffer_BranchDeadlineSet) // Mock Assembler class for testing the AssemblerBufferWithConstantPools // callbacks. namespace { struct TestAssembler; typedef js::jit::AssemblerBufferWithConstantPools< /* SliceSize */ 5 * sizeof(uint32_t), /* InstSize */ 4, /* Inst */ uint32_t, /* Asm */ TestAssembler, /* NumShortBranchRanges */ 3> AsmBufWithPool; struct TestAssembler { // Mock instruction set: // // 0x1111xxxx - align filler instructions. // 0x2222xxxx - manually inserted 'arith' instructions. // 0xaaaaxxxx - noop filler instruction. // 0xb0bbxxxx - branch xxxx bytes forward. (Pool guard). // 0xb1bbxxxx - branch xxxx bytes forward. (Short-range branch). // 0xb2bbxxxx - branch xxxx bytes forward. (Veneer branch). // 0xb3bbxxxx - branch xxxx bytes forward. (Patched short-range branch). // 0xc0ccxxxx - constant pool load (uninitialized). // 0xc1ccxxxx - constant pool load to index xxxx. // 0xc2ccxxxx - constant pool load xxxx bytes ahead. // 0xffffxxxx - pool header with xxxx bytes. static const unsigned BranchRange = 36; static void InsertIndexIntoTag(uint8_t* load_, uint32_t index) { uint32_t* load = reinterpret_cast(load_); MOZ_ASSERT(*load == 0xc0cc0000, "Expected uninitialized constant pool load"); MOZ_ASSERT(index < 0x10000); *load = 0xc1cc0000 + index; } static void PatchConstantPoolLoad(void* loadAddr, void* constPoolAddr) { uint32_t* load = reinterpret_cast(loadAddr); uint32_t index = *load & 0xffff; MOZ_ASSERT(*load == (0xc1cc0000 | index), "Expected constant pool load(index)"); ptrdiff_t offset = reinterpret_cast(constPoolAddr) - reinterpret_cast(loadAddr); offset += index * 4; MOZ_ASSERT(offset % 4 == 0, "Unaligned constant pool"); MOZ_ASSERT(offset > 0 && offset < 0x10000, "Pool out of range"); *load = 0xc2cc0000 + offset; } static void WritePoolGuard(js::jit::BufferOffset branch, uint32_t* dest, js::jit::BufferOffset afterPool) { MOZ_ASSERT(branch.assigned()); MOZ_ASSERT(afterPool.assigned()); size_t branchOff = branch.getOffset(); size_t afterPoolOff = afterPool.getOffset(); MOZ_ASSERT(afterPoolOff > branchOff); uint32_t delta = afterPoolOff - branchOff; *dest = 0xb0bb0000 + delta; } static void WritePoolHeader(void* start, js::jit::Pool* p, bool isNatural) { MOZ_ASSERT(!isNatural, "Natural pool guards not implemented."); uint32_t* hdr = reinterpret_cast(start); *hdr = 0xffff0000 + p->getPoolSize(); } static void PatchShortRangeBranchToVeneer(AsmBufWithPool* buffer, unsigned rangeIdx, js::jit::BufferOffset deadline, js::jit::BufferOffset veneer) { size_t branchOff = deadline.getOffset() - BranchRange; size_t veneerOff = veneer.getOffset(); uint32_t* branch = buffer->getInst(js::jit::BufferOffset(branchOff)); MOZ_ASSERT((*branch & 0xffff0000) == 0xb1bb0000, "Expected short-range branch instruction"); // Copy branch offset to veneer. A real instruction set would require // some adjustment of the label linked-list. *buffer->getInst(veneer) = 0xb2bb0000 | (*branch & 0xffff); MOZ_ASSERT(veneerOff > branchOff, "Veneer should follow branch"); *branch = 0xb3bb0000 + (veneerOff - branchOff); } }; } // namespace BEGIN_TEST(testAssemblerBuffer_AssemblerBufferWithConstantPools) { using js::jit::BufferOffset; AsmBufWithPool ab(/* guardSize= */ 1, /* headerSize= */ 1, /* instBufferAlign(unused)= */ 0, /* poolMaxOffset= */ 17, /* pcBias= */ 0, /* alignFillInst= */ 0x11110000, /* nopFillInst= */ 0xaaaa0000, /* nopFill= */ 0); CHECK(ab.isAligned(16)); CHECK_EQUAL(ab.size(), 0u); CHECK_EQUAL(ab.nextOffset().getOffset(), 0); CHECK(!ab.oom()); // Each slice holds 5 instructions. Trigger a constant pool inside the slice. uint32_t poolLoad[] = {0xc0cc0000}; uint32_t poolData[] = {0xdddd0000, 0xdddd0001, 0xdddd0002, 0xdddd0003}; AsmBufWithPool::PoolEntry pe; BufferOffset load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)poolData, &pe); CHECK_EQUAL(pe.index(), 0u); CHECK_EQUAL(load.getOffset(), 0); // Pool hasn't been emitted yet. Load has been patched by // InsertIndexIntoTag. CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Expected layout: // // 0: load [pc+16] // 4: 0x22220001 // 8: guard branch pc+12 // 12: pool header // 16: poolData // 20: 0x22220002 // ab.putInt(0x22220001); // One could argue that the pool should be flushed here since there is no // more room. However, the current implementation doesn't dump pool until // asked to add data: ab.putInt(0x22220002); CHECK_EQUAL(*ab.getInst(BufferOffset(0)), 0xc2cc0010u); CHECK_EQUAL(*ab.getInst(BufferOffset(4)), 0x22220001u); CHECK_EQUAL(*ab.getInst(BufferOffset(8)), 0xb0bb000cu); CHECK_EQUAL(*ab.getInst(BufferOffset(12)), 0xffff0004u); CHECK_EQUAL(*ab.getInst(BufferOffset(16)), 0xdddd0000u); CHECK_EQUAL(*ab.getInst(BufferOffset(20)), 0x22220002u); // allocEntry() overwrites the load instruction! Restore the original. poolLoad[0] = 0xc0cc0000; // Now try with load and pool data on separate slices. load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)poolData, &pe); CHECK_EQUAL(pe.index(), 1u); // Global pool entry index. CHECK_EQUAL(load.getOffset(), 24); CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Index into current pool. ab.putInt(0x22220001); ab.putInt(0x22220002); CHECK_EQUAL(*ab.getInst(BufferOffset(24)), 0xc2cc0010u); CHECK_EQUAL(*ab.getInst(BufferOffset(28)), 0x22220001u); CHECK_EQUAL(*ab.getInst(BufferOffset(32)), 0xb0bb000cu); CHECK_EQUAL(*ab.getInst(BufferOffset(36)), 0xffff0004u); CHECK_EQUAL(*ab.getInst(BufferOffset(40)), 0xdddd0000u); CHECK_EQUAL(*ab.getInst(BufferOffset(44)), 0x22220002u); // Two adjacent loads to the same pool. poolLoad[0] = 0xc0cc0000; load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)poolData, &pe); CHECK_EQUAL(pe.index(), 2u); // Global pool entry index. CHECK_EQUAL(load.getOffset(), 48); CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Index into current pool. poolLoad[0] = 0xc0cc0000; load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)(poolData + 1), &pe); CHECK_EQUAL(pe.index(), 3u); // Global pool entry index. CHECK_EQUAL(load.getOffset(), 52); CHECK_EQUAL(*ab.getInst(load), 0xc1cc0001); // Index into current pool. ab.putInt(0x22220005); CHECK_EQUAL(*ab.getInst(BufferOffset(48)), 0xc2cc0010u); // load pc+16. CHECK_EQUAL(*ab.getInst(BufferOffset(52)), 0xc2cc0010u); // load pc+16. CHECK_EQUAL(*ab.getInst(BufferOffset(56)), 0xb0bb0010u); // guard branch pc+16. CHECK_EQUAL(*ab.getInst(BufferOffset(60)), 0xffff0008u); // header 8 bytes. CHECK_EQUAL(*ab.getInst(BufferOffset(64)), 0xdddd0000u); // datum 1. CHECK_EQUAL(*ab.getInst(BufferOffset(68)), 0xdddd0001u); // datum 2. CHECK_EQUAL(*ab.getInst(BufferOffset(72)), 0x22220005u); // putInt(0x22220005) // Two loads as above, but the first load has an 8-byte pool entry, and the // second load wouldn't be able to reach its data. This must produce two // pools. poolLoad[0] = 0xc0cc0000; load = ab.allocEntry(1, 2, (uint8_t*)poolLoad, (uint8_t*)(poolData + 2), &pe); CHECK_EQUAL(pe.index(), 4u); // Global pool entry index. CHECK_EQUAL(load.getOffset(), 76); CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Index into current pool. poolLoad[0] = 0xc0cc0000; load = ab.allocEntry(1, 1, (uint8_t*)poolLoad, (uint8_t*)poolData, &pe); CHECK_EQUAL(pe.index(), 6u); // Global pool entry index. (Prev one is two indexes). CHECK_EQUAL(load.getOffset(), 96); CHECK_EQUAL(*ab.getInst(load), 0xc1cc0000); // Index into current pool. CHECK_EQUAL(*ab.getInst(BufferOffset(76)), 0xc2cc000cu); // load pc+12. CHECK_EQUAL(*ab.getInst(BufferOffset(80)), 0xb0bb0010u); // guard branch pc+16. CHECK_EQUAL(*ab.getInst(BufferOffset(84)), 0xffff0008u); // header 8 bytes. CHECK_EQUAL(*ab.getInst(BufferOffset(88)), 0xdddd0002u); // datum 1. CHECK_EQUAL(*ab.getInst(BufferOffset(92)), 0xdddd0003u); // datum 2. // Second pool is not flushed yet, and there is room for one instruction // after the load. Test the keep-together feature. ab.enterNoPool(2); ab.putInt(0x22220006); ab.putInt(0x22220007); ab.leaveNoPool(); CHECK_EQUAL(*ab.getInst(BufferOffset(96)), 0xc2cc000cu); // load pc+16. CHECK_EQUAL(*ab.getInst(BufferOffset(100)), 0xb0bb000cu); // guard branch pc+12. CHECK_EQUAL(*ab.getInst(BufferOffset(104)), 0xffff0004u); // header 4 bytes. CHECK_EQUAL(*ab.getInst(BufferOffset(108)), 0xdddd0000u); // datum 1. CHECK_EQUAL(*ab.getInst(BufferOffset(112)), 0x22220006u); CHECK_EQUAL(*ab.getInst(BufferOffset(116)), 0x22220007u); return true; } END_TEST(testAssemblerBuffer_AssemblerBufferWithConstantPools) BEGIN_TEST(testAssemblerBuffer_AssemblerBufferWithConstantPools_ShortBranch) { using js::jit::BufferOffset; AsmBufWithPool ab(/* guardSize= */ 1, /* headerSize= */ 1, /* instBufferAlign(unused)= */ 0, /* poolMaxOffset= */ 17, /* pcBias= */ 0, /* alignFillInst= */ 0x11110000, /* nopFillInst= */ 0xaaaa0000, /* nopFill= */ 0); // Insert short-range branch. BufferOffset br1 = ab.putInt(0xb1bb00cc); ab.registerBranchDeadline( 1, BufferOffset(br1.getOffset() + TestAssembler::BranchRange)); ab.putInt(0x22220001); BufferOffset off = ab.putInt(0x22220002); ab.registerBranchDeadline( 1, BufferOffset(off.getOffset() + TestAssembler::BranchRange)); ab.putInt(0x22220003); ab.putInt(0x22220004); // Second short-range branch that will be swiped up by hysteresis. BufferOffset br2 = ab.putInt(0xb1bb0d2d); ab.registerBranchDeadline( 1, BufferOffset(br2.getOffset() + TestAssembler::BranchRange)); // Branch should not have been patched yet here. CHECK_EQUAL(*ab.getInst(br1), 0xb1bb00cc); CHECK_EQUAL(*ab.getInst(br2), 0xb1bb0d2d); // Cancel one of the pending branches. // This is what will happen to most branches as they are bound before // expiring by Assembler::bind(). ab.unregisterBranchDeadline( 1, BufferOffset(off.getOffset() + TestAssembler::BranchRange)); off = ab.putInt(0x22220006); // Here we may or may not have patched the branch yet, but it is inevitable // now: // // 0: br1 pc+36 // 4: 0x22220001 // 8: 0x22220002 (unpatched) // 12: 0x22220003 // 16: 0x22220004 // 20: br2 pc+20 // 24: 0x22220006 CHECK_EQUAL(off.getOffset(), 24); // 28: guard branch pc+16 // 32: pool header // 36: veneer1 // 40: veneer2 // 44: 0x22220007 off = ab.putInt(0x22220007); CHECK_EQUAL(off.getOffset(), 44); // Now the branch must have been patched. CHECK_EQUAL(*ab.getInst(br1), 0xb3bb0000 + 36); // br1 pc+36 (patched) CHECK_EQUAL(*ab.getInst(BufferOffset(8)), 0x22220002u); // 0x22220002 (unpatched) CHECK_EQUAL(*ab.getInst(br2), 0xb3bb0000 + 20); // br2 pc+20 (patched) CHECK_EQUAL(*ab.getInst(BufferOffset(28)), 0xb0bb0010u); // br pc+16 (guard) CHECK_EQUAL(*ab.getInst(BufferOffset(32)), 0xffff0000u); // pool header 0 bytes. CHECK_EQUAL(*ab.getInst(BufferOffset(36)), 0xb2bb00ccu); // veneer1 w/ original 'cc' offset. CHECK_EQUAL(*ab.getInst(BufferOffset(40)), 0xb2bb0d2du); // veneer2 w/ original 'd2d' offset. CHECK_EQUAL(*ab.getInst(BufferOffset(44)), 0x22220007u); return true; } END_TEST(testAssemblerBuffer_AssemblerBufferWithConstantPools_ShortBranch) // Test that everything is put together correctly in the ARM64 assembler. #if defined(JS_CODEGEN_ARM64) # include "jit/MacroAssembler-inl.h" BEGIN_TEST(testAssemblerBuffer_ARM64) { using namespace js::jit; js::LifoAlloc lifo(4096); TempAllocator alloc(&lifo); JitContext jc(cx); StackMacroAssembler masm(cx, alloc); AutoCreatedBy acb(masm, __func__); // Branches to an unbound label. Label lab1; masm.branch(Assembler::Equal, &lab1); masm.branch(Assembler::LessThan, &lab1); masm.bind(&lab1); masm.branch(Assembler::Equal, &lab1); CHECK_EQUAL(masm.getInstructionAt(BufferOffset(0))->InstructionBits(), vixl::B_cond | vixl::Assembler::ImmCondBranch(2) | vixl::eq); CHECK_EQUAL(masm.getInstructionAt(BufferOffset(4))->InstructionBits(), vixl::B_cond | vixl::Assembler::ImmCondBranch(1) | vixl::lt); CHECK_EQUAL(masm.getInstructionAt(BufferOffset(8))->InstructionBits(), vixl::B_cond | vixl::Assembler::ImmCondBranch(0) | vixl::eq); // Branches can reach the label, but the linked list of uses needs to be // rearranged. The final conditional branch cannot reach the first branch. Label lab2a; Label lab2b; masm.bind(&lab2a); masm.B(&lab2b); // Generate 1,100,000 bytes of NOPs. for (unsigned n = 0; n < 1100000; n += 4) { masm.Nop(); } masm.branch(Assembler::LessThan, &lab2b); masm.bind(&lab2b); CHECK_EQUAL( masm.getInstructionAt(BufferOffset(lab2a.offset()))->InstructionBits(), vixl::B | vixl::Assembler::ImmUncondBranch(1100000 / 4 + 2)); CHECK_EQUAL(masm.getInstructionAt(BufferOffset(lab2b.offset() - 4)) ->InstructionBits(), vixl::B_cond | vixl::Assembler::ImmCondBranch(1) | vixl::lt); // Generate a conditional branch that can't reach its label. Label lab3a; Label lab3b; masm.bind(&lab3a); masm.branch(Assembler::LessThan, &lab3b); for (unsigned n = 0; n < 1100000; n += 4) { masm.Nop(); } masm.bind(&lab3b); masm.B(&lab3a); Instruction* bcond3 = masm.getInstructionAt(BufferOffset(lab3a.offset())); CHECK_EQUAL(bcond3->BranchType(), vixl::CondBranchType); ptrdiff_t delta = bcond3->ImmPCRawOffset() * 4; Instruction* veneer = masm.getInstructionAt(BufferOffset(lab3a.offset() + delta)); CHECK_EQUAL(veneer->BranchType(), vixl::UncondBranchType); delta += veneer->ImmPCRawOffset() * 4; CHECK_EQUAL(delta, lab3b.offset() - lab3a.offset()); Instruction* b3 = masm.getInstructionAt(BufferOffset(lab3b.offset())); CHECK_EQUAL(b3->BranchType(), vixl::UncondBranchType); CHECK_EQUAL(4 * b3->ImmPCRawOffset(), -delta); return true; } END_TEST(testAssemblerBuffer_ARM64) #endif /* JS_CODEGEN_ARM64 */