summaryrefslogtreecommitdiffstats
path: root/js/src/jsapi-tests/testAssemblerBuffer.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /js/src/jsapi-tests/testAssemblerBuffer.cpp
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/jsapi-tests/testAssemblerBuffer.cpp')
-rw-r--r--js/src/jsapi-tests/testAssemblerBuffer.cpp587
1 files changed, 587 insertions, 0 deletions
diff --git a/js/src/jsapi-tests/testAssemblerBuffer.cpp b/js/src/jsapi-tests/testAssemblerBuffer.cpp
new file mode 100644
index 0000000000..50fc5b6043
--- /dev/null
+++ b/js/src/jsapi-tests/testAssemblerBuffer.cpp
@@ -0,0 +1,587 @@
+/* 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 <stdlib.h>
+
+#include "jit/shared/IonAssemblerBufferWithConstantPools.h"
+#include "jsapi-tests/tests.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<uint32_t*>(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<uint32_t*>(loadAddr);
+ uint32_t index = *load & 0xffff;
+ MOZ_ASSERT(*load == (0xc1cc0000 | index),
+ "Expected constant pool load(index)");
+ ptrdiff_t offset = reinterpret_cast<uint8_t*>(constPoolAddr) -
+ reinterpret_cast<uint8_t*>(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<uint32_t*>(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 */