blob: 83134e61644f0da434c18efe3a715d07d695563e [file] [log] [blame]
// 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 <cstdio>
#include <iostream>
#include <string>
#include "test-runner.h"
#include "test-utils.h"
#include "aarch32/test-utils-aarch32.h"
#include "aarch32/disasm-aarch32.h"
#include "aarch32/macro-assembler-aarch32.h"
namespace vixl {
namespace aarch32 {
#define STRINGIFY(x) #x
#ifdef VIXL_INCLUDE_TARGET_A32_ONLY
#define TEST_T32(Name) \
void Test##Name##Impl(InstructionSet isa __attribute__((unused)))
#else
// Tests declared with this macro will only target T32.
#define TEST_T32(Name) \
void Test##Name##Impl(InstructionSet isa); \
void Test##Name() { Test##Name##Impl(T32); } \
Test test_##Name(STRINGIFY(AARCH32_T32_##Name), &Test##Name); \
void Test##Name##Impl(InstructionSet isa __attribute__((unused)))
#endif
#ifdef VIXL_INCLUDE_TARGET_T32_ONLY
#define TEST_A32(Name) \
void Test##Name##Impl(InstructionSet isa __attribute__((unused)))
#else
// Test declared with this macro will only target A32.
#define TEST_A32(Name) \
void Test##Name##Impl(InstructionSet isa); \
void Test##Name() { Test##Name##Impl(A32); } \
Test test_##Name(STRINGIFY(AARCH32_A32_##Name), &Test##Name); \
void Test##Name##Impl(InstructionSet isa __attribute__((unused)))
#endif
// Tests declared with this macro will be run twice: once targeting A32 and
// once targeting T32.
#if defined(VIXL_INCLUDE_TARGET_A32_ONLY)
#define TEST(Name) TEST_A32(Name)
#elif defined(VIXL_INCLUDE_TARGET_T32_ONLY)
#define TEST(Name) TEST_T32(Name)
#else
#define TEST(Name) \
void Test##Name##Impl(InstructionSet isa); \
void Test##Name() { \
Test##Name##Impl(A32); \
printf(" > A32 done\n"); \
Test##Name##Impl(T32); \
printf(" > T32 done\n"); \
} \
Test test_##Name(STRINGIFY(AARCH32_ASM_##Name), &Test##Name); \
void Test##Name##Impl(InstructionSet isa __attribute__((unused)))
#endif
// Tests declared with this macro are not expected to use any provided test
// helpers such as SETUP, RUN, etc.
#define TEST_NOASM(Name) \
void Test##Name(); \
Test test_##Name(STRINGIFY(AARCH32_##Name), &Test##Name); \
void Test##Name()
#define __ masm.
#define __TESTOBJ test.
#define BUF_SIZE (4096)
#define CHECK_POOL_SIZE(size) \
do { \
VIXL_CHECK(__TESTOBJ GetPoolSize() == size); \
} while (false)
#ifdef VIXL_INCLUDE_SIMULATOR_AARCH32
// No simulator yet.
#define SETUP() \
MacroAssembler masm(BUF_SIZE, isa); \
TestMacroAssembler test(&masm);
#define START() masm.GetBuffer()->Reset();
#define END() \
__ Hlt(0); \
__ FinalizeCode();
#define RUN() DISASSEMBLE();
#else // ifdef VIXL_INCLUDE_SIMULATOR_AARCH32.
#define SETUP() \
RegisterDump core; \
MacroAssembler masm(BUF_SIZE, isa); \
TestMacroAssembler test(&masm); \
UseScratchRegisterScope harness_scratch;
#define START() \
harness_scratch.Open(&masm); \
harness_scratch.ExcludeAll(); \
masm.GetBuffer()->Reset(); \
__ Push(r4); \
__ Push(r5); \
__ Push(r6); \
__ Push(r7); \
__ Push(r8); \
__ Push(r9); \
__ Push(r10); \
__ Push(r11); \
__ Push(ip); \
__ Push(lr); \
__ Mov(r0, 0); \
__ Msr(APSR_nzcvq, r0); \
__ Vmsr(FPSCR, r0); \
harness_scratch.Include(ip);
#define END() \
harness_scratch.Exclude(ip); \
core.Dump(&masm); \
__ Pop(lr); \
__ Pop(ip); \
__ Pop(r11); \
__ Pop(r10); \
__ Pop(r9); \
__ Pop(r8); \
__ Pop(r7); \
__ Pop(r6); \
__ Pop(r5); \
__ Pop(r4); \
__ Bx(lr); \
__ FinalizeCode(); \
harness_scratch.Close();
// Execute the generated code from the MacroAssembler's automatic code buffer.
// Note the offset for ExecuteMemory since the PCS requires that
// the address be odd in the case of branching to T32 code.
#define RUN() \
DISASSEMBLE(); \
{ \
int pcs_offset = masm.IsUsingT32() ? 1 : 0; \
masm.GetBuffer()->SetExecutable(); \
ExecuteMemory(masm.GetBuffer()->GetStartAddress<byte*>(), \
masm.GetSizeOfCodeGenerated(), \
pcs_offset); \
masm.GetBuffer()->SetWritable(); \
}
#endif // ifdef VIXL_INCLUDE_SIMULATOR_AARCH32
#ifdef VIXL_INCLUDE_SIMULATOR_AARCH32
// No simulator yet. We can't test the results.
#define ASSERT_EQUAL_32(expected, result)
#define ASSERT_EQUAL_64(expected, result)
#define ASSERT_EQUAL_128(expected_h, expected_l, result)
#define ASSERT_EQUAL_FP32(expected, result)
#define ASSERT_EQUAL_FP64(expected, result)
#define ASSERT_EQUAL_NZCV(expected)
#else
#define ASSERT_EQUAL_32(expected, result) \
VIXL_CHECK(Equal32(expected, &core, result))
#define ASSERT_EQUAL_64(expected, result) \
VIXL_CHECK(Equal64(expected, &core, result))
#define ASSERT_EQUAL_128(expected_h, expected_l, result) \
VIXL_CHECK(Equal128(expected_h, expected_l, &core, result))
#define ASSERT_EQUAL_FP32(expected, result) \
VIXL_CHECK(EqualFP32(expected, &core, result))
#define ASSERT_EQUAL_FP64(expected, result) \
VIXL_CHECK(EqualFP64(expected, &core, result))
#define ASSERT_EQUAL_NZCV(expected) \
VIXL_CHECK(EqualNzcv(expected, core.flags_nzcv()))
#endif
#define DISASSEMBLE() \
if (Test::disassemble()) { \
PrintDisassembler dis(std::cout, 0); \
if (masm.IsUsingT32()) { \
dis.DisassembleT32Buffer(masm.GetBuffer()->GetStartAddress<uint16_t*>(), \
masm.GetCursorOffset()); \
} else { \
dis.DisassembleA32Buffer(masm.GetBuffer()->GetStartAddress<uint32_t*>(), \
masm.GetCursorOffset()); \
} \
}
// TODO: Add SBC to the ADC tests.
TEST(adc_shift) {
SETUP();
START();
// Initialize registers.
__ Mov(r0, 0);
__ Mov(r1, 1);
__ Mov(r2, 0x01234567);
__ Mov(r3, 0xfedcba98);
// Clear the C flag.
__ Adds(r0, r0, 0);
__ Adc(r4, r2, r3);
__ Adc(r5, r0, Operand(r1, LSL, 30));
__ Adc(r6, r0, Operand(r2, LSR, 16));
__ Adc(r7, r2, Operand(r3, ASR, 4));
__ Adc(r8, r2, Operand(r3, ROR, 8));
__ Adc(r9, r2, Operand(r3, RRX));
END();
RUN();
ASSERT_EQUAL_32(0xffffffff, r4);
ASSERT_EQUAL_32(INT32_C(1) << 30, r5);
ASSERT_EQUAL_32(0x00000123, r6);
ASSERT_EQUAL_32(0x01111110, r7);
ASSERT_EQUAL_32(0x9a222221, r8);
ASSERT_EQUAL_32(0x8091a2b3, r9);
START();
// Initialize registers.
__ Mov(r0, 0);
__ Mov(r1, 1);
__ Mov(r2, 0x01234567);
__ Mov(r3, 0xfedcba98);
__ Mov(r4, 0xffffffff);
// Set the C flag.
__ Adds(r0, r4, r1);
__ Adc(r5, r2, r3);
__ Adc(r6, r0, Operand(r1, LSL, 30));
__ Adc(r7, r0, Operand(r2, LSR, 16));
__ Adc(r8, r2, Operand(r3, ASR, 4));
__ Adc(r9, r2, Operand(r3, ROR, 8));
__ Adc(r10, r2, Operand(r3, RRX));
END();
RUN();
ASSERT_EQUAL_32(0xffffffff + 1, r5);
ASSERT_EQUAL_32((INT32_C(1) << 30) + 1, r6);
ASSERT_EQUAL_32(0x00000123 + 1, r7);
ASSERT_EQUAL_32(0x01111110 + 1, r8);
ASSERT_EQUAL_32(0x9a222221 + 1, r9);
ASSERT_EQUAL_32(0x0091a2b3 + 1, r10);
// Check that adc correctly sets the condition flags.
START();
__ Mov(r0, 0);
__ Mov(r1, 0xffffffff);
__ Mov(r2, 1);
// Clear the C flag.
__ Adds(r0, r0, 0);
__ Adcs(r3, r2, r1);
END();
RUN();
ASSERT_EQUAL_NZCV(ZCFlag);
ASSERT_EQUAL_32(0, r3);
START();
__ Mov(r0, 0);
__ Mov(r1, 0x80000000);
__ Mov(r2, 1);
// Clear the C flag.
__ Adds(r0, r0, 0);
__ Adcs(r3, r2, Operand(r1, ASR, 31));
END();
RUN();
ASSERT_EQUAL_NZCV(ZCFlag);
ASSERT_EQUAL_32(0, r3);
START();
__ Mov(r0, 0);
__ Mov(r1, 0x80000000);
__ Mov(r2, 0xffffffff);
// Clear the C flag.
__ Adds(r0, r0, 0);
__ Adcs(r3, r2, Operand(r1, LSR, 31));
END();
RUN();
ASSERT_EQUAL_NZCV(ZCFlag);
ASSERT_EQUAL_32(0, r3);
START();
__ Mov(r0, 0);
__ Mov(r1, 0x07ffffff);
__ Mov(r2, 0x10);
// Clear the C flag.
__ Adds(r0, r0, 0);
__ Adcs(r3, r2, Operand(r1, LSL, 4));
END();
RUN();
ASSERT_EQUAL_NZCV(NVFlag);
ASSERT_EQUAL_32(0x080000000, r3);
START();
__ Mov(r0, 0);
__ Mov(r1, 0xffffff00);
__ Mov(r2, 0xff000001);
// Clear the C flag.
__ Adds(r0, r0, 0);
__ Adcs(r3, r2, Operand(r1, ROR, 8));
END();
RUN();
ASSERT_EQUAL_NZCV(ZCFlag);
ASSERT_EQUAL_32(0, r3);
START();
__ Mov(r0, 0);
__ Mov(r1, 0xffffffff);
__ Mov(r2, 0x1);
// Clear the C flag, forcing RRX to insert 0 in r1's most significant bit.
__ Adds(r0, r0, 0);
__ Adcs(r3, r2, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_NZCV(NVFlag);
ASSERT_EQUAL_32(0x80000000, r3);
START();
__ Mov(r0, 0);
__ Mov(r1, 0xffffffff);
__ Mov(r2, 0x1);
// Set the C flag, forcing RRX to insert 1 in r1's most significant bit.
__ Adds(r0, r1, r2);
__ Adcs(r3, r2, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_NZCV(CFlag);
ASSERT_EQUAL_32(1, r3);
}
TEST(adc_wide_imm) {
SETUP();
START();
__ Mov(r0, 0);
// Clear the C flag.
__ Adds(r0, r0, 0);
__ Adc(r1, r0, 0x12345678);
__ Adc(r2, r0, 0xffffffff);
// Set the C flag.
__ Cmp(r0, r0);
__ Adc(r3, r0, 0x12345678);
__ Adc(r4, r0, 0xffffffff);
END();
RUN();
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0xffffffff, r2);
ASSERT_EQUAL_32(0x12345678 + 1, r3);
ASSERT_EQUAL_32(0, r4);
}
// TODO: Add SUB tests to the ADD tests.
TEST(add_imm) {
SETUP();
START();
__ Mov(r0, 0);
__ Mov(r1, 0x1111);
__ Mov(r2, 0xffffffff);
__ Mov(r3, 0x80000000);
__ Add(r4, r0, 0x12);
__ Add(r5, r1, 0x120000);
__ Add(r6, r0, 0xab << 12);
__ Add(r7, r2, 1);
END();
RUN();
ASSERT_EQUAL_32(0x12, r4);
ASSERT_EQUAL_32(0x121111, r5);
ASSERT_EQUAL_32(0xab000, r6);
ASSERT_EQUAL_32(0x0, r7);
}
TEST(add_wide_imm) {
SETUP();
START();
__ Mov(r0, 0);
__ Mov(r1, 1);
__ Add(r2, r0, 0x12345678);
__ Add(r3, r1, 0xffff);
END();
RUN();
ASSERT_EQUAL_32(0x12345678, r2);
ASSERT_EQUAL_32(0x00010000, r3);
}
TEST(add_shifted) {
SETUP();
START();
__ Mov(r0, 0);
__ Mov(r1, 0x01234567);
__ Mov(r2, 0x76543210);
__ Mov(r3, 0xffffffff);
__ Add(r4, r1, r2);
__ Add(r5, r0, Operand(r1, LSL, 8));
__ Add(r6, r0, Operand(r1, LSR, 8));
__ Add(r7, r0, Operand(r1, ASR, 8));
__ Add(r8, r3, Operand(r1, ROR, 8));
// Set the C flag.
__ Adds(r0, r3, 1);
__ Add(r9, r3, Operand(r1, RRX));
// Clear the C flag.
__ Adds(r0, r0, 0);
__ Add(r10, r3, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_32(0x77777777, r4);
ASSERT_EQUAL_32(0x23456700, r5);
ASSERT_EQUAL_32(0x00012345, r6);
ASSERT_EQUAL_32(0x00012345, r7);
ASSERT_EQUAL_32(0x67012344, r8);
ASSERT_EQUAL_32(0x8091a2b2, r9);
ASSERT_EQUAL_32(0x0091a2b2, r10);
}
TEST(and_) {
SETUP();
START();
__ Mov(r0, 0x0000fff0);
__ Mov(r1, 0xf00000ff);
__ Mov(r2, 0xffffffff);
__ And(r3, r0, r1);
__ And(r4, r0, Operand(r1, LSL, 4));
__ And(r5, r0, Operand(r1, LSR, 1));
__ And(r6, r0, Operand(r1, ASR, 20));
__ And(r7, r0, Operand(r1, ROR, 28));
__ And(r8, r0, 0xff);
// Set the C flag.
__ Adds(r9, r2, 1);
__ And(r9, r1, Operand(r1, RRX));
// Clear the C flag.
__ Adds(r10, r0, 0);
__ And(r10, r1, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_32(0x000000f0, r3);
ASSERT_EQUAL_32(0x00000ff0, r4);
ASSERT_EQUAL_32(0x00000070, r5);
ASSERT_EQUAL_32(0x0000ff00, r6);
ASSERT_EQUAL_32(0x00000ff0, r7);
ASSERT_EQUAL_32(0x000000f0, r8);
ASSERT_EQUAL_32(0xf000007f, r9);
ASSERT_EQUAL_32(0x7000007f, r10);
}
TEST(ands) {
SETUP();
START();
__ Mov(r0, 0);
__ Mov(r1, 0xf00000ff);
__ Ands(r0, r1, r1);
END();
RUN();
ASSERT_EQUAL_NZCV(NFlag);
ASSERT_EQUAL_32(0xf00000ff, r0);
START();
__ Mov(r0, 0x00fff000);
__ Mov(r1, 0xf00000ff);
__ Ands(r0, r0, Operand(r1, LSL, 4));
END();
RUN();
ASSERT_EQUAL_NZCV(ZCFlag);
ASSERT_EQUAL_32(0x00000000, r0);
START();
__ Mov(r0, 0x0000fff0);
__ Mov(r1, 0xf00000ff);
__ Ands(r0, r0, Operand(r1, LSR, 4));
END();
RUN();
ASSERT_EQUAL_NZCV(ZCFlag);
ASSERT_EQUAL_32(0x00000000, r0);
START();
__ Mov(r0, 0xf000fff0);
__ Mov(r1, 0xf00000ff);
__ Ands(r0, r0, Operand(r1, ASR, 4));
END();
RUN();
ASSERT_EQUAL_NZCV(NCFlag);
ASSERT_EQUAL_32(0xf0000000, r0);
START();
__ Mov(r0, 0x80000000);
__ Mov(r1, 0x00000001);
__ Ands(r0, r0, Operand(r1, ROR, 1));
END();
RUN();
ASSERT_EQUAL_NZCV(NCFlag);
ASSERT_EQUAL_32(0x80000000, r0);
START();
__ Mov(r0, 0x80000000);
__ Mov(r1, 0x80000001);
// Clear the C flag, forcing RRX to insert 0 in r1's most significant bit.
__ Adds(r2, r0, 0);
__ Ands(r2, r0, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_NZCV(ZCFlag);
ASSERT_EQUAL_32(0, r2);
START();
__ Mov(r0, 0x80000000);
__ Mov(r1, 0x80000001);
__ Mov(r2, 0xffffffff);
// Set the C flag, forcing RRX to insert 1 in r1's most significant bit.
__ Adds(r2, r2, 1);
__ Ands(r2, r0, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_NZCV(NCFlag);
ASSERT_EQUAL_32(0x80000000, r2);
START();
__ Mov(r0, 0xfff0);
__ Ands(r0, r0, 0xf);
END();
RUN();
ASSERT_EQUAL_NZCV(ZFlag);
ASSERT_EQUAL_32(0x00000000, r0);
START();
__ Mov(r0, 0xff000000);
__ Ands(r0, r0, 0x80000000);
END();
RUN();
ASSERT_EQUAL_NZCV(NCFlag);
ASSERT_EQUAL_32(0x80000000, r0);
}
TEST(adr_in_range) {
SETUP();
Label label_1, label_2, label_3, label_4;
START();
{
size_t size_of_generated_code;
if (masm.IsUsingA32()) {
size_of_generated_code = 18 * kA32InstructionSizeInBytes;
} else {
size_of_generated_code = 18 * k32BitT32InstructionSizeInBytes +
3 * k16BitT32InstructionSizeInBytes;
}
ExactAssemblyScope scope(&masm,
size_of_generated_code,
ExactAssemblyScope::kExactSize);
__ mov(r0, 0x0); // Set to zero to indicate success.
__ adr(r1, &label_3);
__ adr(r2, &label_1); // Multiple forward references to the same label.
__ adr(r3, &label_1);
__ adr(r4, &label_1);
__ bind(&label_2);
__ eor(r5, r2, r3); // Ensure that r2, r3 and r4 are identical.
__ eor(r6, r2, r4);
__ orr(r0, r5, r6);
if (masm.IsUsingT32()) {
// The jump target needs to have its least significant bit set to indicate
// that we are jumping into thumb mode.
__ orr(r2, r2, 1);
}
__ bx(r2); // label_1, label_3
__ bind(&label_3);
__ adr(r2, &label_3); // Self-reference (offset 0).
__ eor(r1, r1, r2);
__ adr(r2, &label_4); // Simple forward reference.
if (masm.IsUsingT32()) {
// The jump target needs to have its least significant bit set to indicate
// that we are jumping into thumb mode.
__ orr(r2, r2, 1);
}
__ bx(r2); // label_4
__ bind(&label_1);
__ adr(r2, &label_3); // Multiple reverse references to the same label.
__ adr(r3, &label_3);
__ adr(r4, &label_3);
__ adr(r5, &label_2); // Simple reverse reference.
if (masm.IsUsingT32()) {
// The jump target needs to have its least significant bit set to indicate
// that we are jumping into thumb mode.
__ orr(r5, r5, 1);
}
__ bx(r5); // label_2
__ bind(&label_4);
}
END();
RUN();
ASSERT_EQUAL_32(0x0, r0);
ASSERT_EQUAL_32(0x0, r1);
}
// Check that we can use adr with any alignement.
TEST(adr_unaligned) {
SETUP();
Label label_end;
START();
{
Location label_0, label_1, label_2, label_3;
// 5 instructions.
ExactAssemblyScope scope(&masm,
5 * kA32InstructionSizeInBytes + 4,
ExactAssemblyScope::kExactSize);
__ adr(Wide, r0, &label_0);
__ adr(Wide, r1, &label_1);
__ adr(Wide, r2, &label_2);
__ adr(Wide, r3, &label_3);
__ b(Wide, &label_end);
__ bind(&label_0);
__ GetBuffer()->EmitData("a", 1);
__ bind(&label_1);
__ GetBuffer()->EmitData("b", 1);
__ bind(&label_2);
__ GetBuffer()->EmitData("c", 1);
__ bind(&label_3);
__ GetBuffer()->EmitData("d", 1);
}
{
__ Bind(&label_end);
__ Ldrb(r0, MemOperand(r0));
__ Ldrb(r1, MemOperand(r1));
__ Ldrb(r2, MemOperand(r2));
__ Ldrb(r3, MemOperand(r3));
}
END();
RUN();
ASSERT_EQUAL_32('a', r0);
ASSERT_EQUAL_32('b', r1);
ASSERT_EQUAL_32('c', r2);
ASSERT_EQUAL_32('d', r3);
}
TEST(shift_imm) {
SETUP();
START();
__ Mov(r0, 0);
__ Mov(r1, 0xfedcba98);
__ Mov(r2, 0xffffffff);
__ Lsl(r3, r1, 4);
__ Lsr(r4, r1, 8);
__ Asr(r5, r1, 16);
__ Ror(r6, r1, 20);
END();
RUN();
ASSERT_EQUAL_32(0xedcba980, r3);
ASSERT_EQUAL_32(0x00fedcba, r4);
ASSERT_EQUAL_32(0xfffffedc, r5);
ASSERT_EQUAL_32(0xcba98fed, r6);
}
TEST(shift_reg) {
SETUP();
START();
__ Mov(r0, 0);
__ Mov(r1, 0xfedcba98);
__ Mov(r2, 0xffffffff);
__ Add(r9, r0, 4);
__ Lsl(r3, r1, r9);
__ Add(r9, r0, 8);
__ Lsr(r4, r1, r9);
__ Add(r9, r0, 16);
__ Asr(r5, r1, r9);
__ Add(r9, r0, 20);
__ Ror(r6, r1, r9);
// Set the C flag.
__ Adds(r7, r2, 1);
__ Rrx(r7, r1);
// Clear the C flag.
__ Adds(r8, r0, 0);
__ Rrx(r8, r1);
END();
RUN();
ASSERT_EQUAL_32(0xedcba980, r3);
ASSERT_EQUAL_32(0x00fedcba, r4);
ASSERT_EQUAL_32(0xfffffedc, r5);
ASSERT_EQUAL_32(0xcba98fed, r6);
ASSERT_EQUAL_32(0xff6e5d4c, r7);
ASSERT_EQUAL_32(0x7f6e5d4c, r8);
}
TEST(branch_cond) {
SETUP();
Label done, wrong;
START();
__ Mov(r0, 0x0);
__ Mov(r1, 0x1);
__ Mov(r2, 0x80000000);
// TODO: Use r0 instead of r3 when r0 becomes available.
__ Mov(r3, 0x1);
// For each 'cmp' instruction below, condition codes other than the ones
// following it would branch.
__ Cmp(r1, 0);
__ B(eq, &wrong);
__ B(lo, &wrong);
__ B(mi, &wrong);
__ B(vs, &wrong);
__ B(ls, &wrong);
__ B(lt, &wrong);
__ B(le, &wrong);
Label ok_1;
__ B(ne, &ok_1);
// TODO: Use __ Mov(r0, 0x0) instead.
__ Add(r3, r0, 0x0);
__ Bind(&ok_1);
__ Cmp(r1, 1);
__ B(ne, &wrong);
__ B(lo, &wrong);
__ B(mi, &wrong);
__ B(vs, &wrong);
__ B(hi, &wrong);
__ B(lt, &wrong);
__ B(gt, &wrong);
Label ok_2;
__ B(pl, &ok_2);
// TODO: Use __ Mov(r0, 0x0) instead.
__ Add(r3, r0, 0x0);
__ Bind(&ok_2);
__ Cmp(r1, 2);
__ B(eq, &wrong);
__ B(hs, &wrong);
__ B(pl, &wrong);
__ B(vs, &wrong);
__ B(hi, &wrong);
__ B(ge, &wrong);
__ B(gt, &wrong);
Label ok_3;
__ B(vc, &ok_3);
// TODO: Use __ Mov(r0, 0x0) instead.
__ Add(r3, r0, 0x0);
__ Bind(&ok_3);
__ Cmp(r2, 1);
__ B(eq, &wrong);
__ B(lo, &wrong);
__ B(mi, &wrong);
__ B(vc, &wrong);
__ B(ls, &wrong);
__ B(ge, &wrong);
__ B(gt, &wrong);
Label ok_4;
__ B(le, &ok_4);
// TODO: Use __ Mov(r0, 0x0) instead.
__ Add(r3, r0, 0x0);
__ Bind(&ok_4);
Label ok_5;
__ B(&ok_5);
// TODO: Use __ Mov(r0, 0x0) instead.
__ Add(r3, r0, 0x0);
__ Bind(&ok_5);
__ B(&done);
__ Bind(&wrong);
// TODO: Use __ Mov(r0, 0x0) instead.
__ Add(r3, r0, 0x0);
__ Bind(&done);
END();
RUN();
// TODO: Use r0.
ASSERT_EQUAL_32(0x1, r3);
}
TEST(bfc_bfi) {
SETUP();
START();
__ Mov(r0, 0xffffffff);
__ Mov(r1, 0x01234567);
__ Mov(r2, 0x0);
__ Bfc(r0, 0, 3);
__ Bfc(r0, 16, 5);
__ Bfi(r2, r1, 0, 8);
__ Bfi(r2, r1, 16, 16);
END();
RUN();
ASSERT_EQUAL_32(0xffe0fff8, r0);
ASSERT_EQUAL_32(0x45670067, r2);
}
TEST(bic) {
SETUP();
START();
__ Mov(r0, 0xfff0);
__ Mov(r1, 0xf00000ff);
__ Mov(r2, 0xffffffff);
__ Bic(r3, r0, r1);
__ Bic(r4, r0, Operand(r1, LSL, 4));
__ Bic(r5, r0, Operand(r1, LSR, 1));
__ Bic(r6, r0, Operand(r1, ASR, 20));
__ Bic(r7, r0, Operand(r1, ROR, 28));
__ Bic(r8, r0, 0x1f);
// Set the C flag.
__ Adds(r9, r2, 1);
__ Bic(r9, r1, Operand(r1, RRX));
// Clear the C flag.
__ Adds(r10, r0, 0);
__ Bic(r10, r1, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_32(0x0000ff00, r3);
ASSERT_EQUAL_32(0x0000f000, r4);
ASSERT_EQUAL_32(0x0000ff80, r5);
ASSERT_EQUAL_32(0x000000f0, r6);
ASSERT_EQUAL_32(0x0000f000, r7);
ASSERT_EQUAL_32(0x0000ffe0, r8);
ASSERT_EQUAL_32(0x00000080, r9);
ASSERT_EQUAL_32(0x80000080, r10);
}
TEST(bics) {
SETUP();
START();
__ Mov(r0, 0);
__ Mov(r1, 0xf00000ff);
__ Bics(r0, r1, r1);
END();
RUN();
ASSERT_EQUAL_NZCV(ZFlag);
ASSERT_EQUAL_32(0, r0);
START();
__ Mov(r0, 0x00fff000);
__ Mov(r1, 0x0fffff00);
__ Bics(r0, r0, Operand(r1, LSL, 4));
END();
RUN();
ASSERT_EQUAL_NZCV(ZFlag);
ASSERT_EQUAL_32(0x00000000, r0);
START();
__ Mov(r0, 0x0000fff0);
__ Mov(r1, 0x0fffff00);
__ Bics(r0, r0, Operand(r1, LSR, 4));
END();
RUN();
ASSERT_EQUAL_NZCV(ZFlag);
ASSERT_EQUAL_32(0x00000000, r0);
START();
__ Mov(r0, 0xf000fff0);
__ Mov(r1, 0x0fffff00);
__ Bics(r0, r0, Operand(r1, ASR, 4));
END();
RUN();
ASSERT_EQUAL_NZCV(NFlag);
ASSERT_EQUAL_32(0xf0000000, r0);
START();
__ Mov(r0, 0x80000000);
__ Mov(r1, 0xfffffffe);
__ Bics(r0, r0, Operand(r1, ROR, 1));
END();
RUN();
ASSERT_EQUAL_NZCV(NFlag);
ASSERT_EQUAL_32(0x80000000, r0);
START();
__ Mov(r0, 0x80000000);
__ Mov(r1, 0x80000001);
// Clear the C flag, forcing RRX to insert 0 in r1's most significant bit.
__ Adds(r2, r0, 0);
__ Bics(r2, r0, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_NZCV(NCFlag);
ASSERT_EQUAL_32(0x80000000, r2);
START();
__ Mov(r0, 0x80000000);
__ Mov(r1, 0x80000001);
__ Mov(r2, 0xffffffff);
// Set the C flag, forcing RRX to insert 1 in r1's most significant bit.
__ Adds(r2, r2, 1);
__ Bics(r2, r0, Operand(r1, RRX));
END();
RUN();
ASSERT_EQUAL_NZCV(ZCFlag);
ASSERT_EQUAL_32(0, r2);
START();
__ Mov(r0, 0xf000);
__ Bics(r0, r0, 0xf000);
END();
RUN();
ASSERT_EQUAL_NZCV(ZFlag);
ASSERT_EQUAL_32(0x00000000, r0);
START();
__ Mov(r0, 0xff000000);
__ Bics(r0, r0, 0x7fffffff);
END();
RUN();
ASSERT_EQUAL_NZCV(NFlag);
ASSERT_EQUAL_32(0x80000000, r0);
}
// Make sure calling a macro-assembler instruction will generate literal pools
// if needed.
TEST_T32(veneer_pool_generated_by_macro_instruction) {
SETUP();
START();
Label start, end;
VIXL_CHECK(test.PoolIsEmpty());
__ Mov(r0, 1);
__ Bind(&start);
__ Cbz(r0, &end);
VIXL_CHECK(!test.PoolIsEmpty());
// Generate enough code so that, after the loop, no instruction can be
// generated before we need to generate the veneer pool.
// Use `ExactAssemblyScope` and the assembler to generate the code.
int32_t space = test.GetPoolCheckpoint() - masm.GetCursorOffset();
{
ExactAssemblyScope scope(&masm, space, ExactAssemblyScope::kExactSize);
while (space > 0) {
__ nop();
space -= k16BitT32InstructionSizeInBytes;
}
}
// We should not have emitted the pool at this point.
VIXL_CHECK(!test.PoolIsEmpty());
VIXL_CHECK(test.GetPoolCheckpoint() == masm.GetCursorOffset());
// Now the pool will need to be generated before we can emit anything.
Label check;
__ Bind(&check);
__ Mov(r0, 0);
// We should have generated 3 wide instructions:
// b.w past_veneer_pool
// b.w end ;; veneer from CBZ to "end".
// past_veneer_pool:
// mov r0, #0
VIXL_CHECK(masm.GetSizeOfCodeGeneratedSince(&check) ==
(3 * k32BitT32InstructionSizeInBytes));
// Branch back to make sure the veneers work.
__ B(&start);
__ Bind(&end);
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
ASSERT_EQUAL_32(0, r0);
}
// NOTE: This test has needed modifications for the new pool manager, as it
// was testing a corner case of the previous pool managers. We keep it as
// another testcase.
TEST(emit_reused_load_literal) {
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
const int ldrd_range = masm.IsUsingA32() ? 255 : 1020;
const int string_size = AlignUp(ldrd_range + kMaxInstructionSizeInBytes, 4);
std::string test_string(string_size, 'x');
StringLiteral big_literal(test_string.c_str());
__ Adr(r4, &big_literal);
// This load has a wider range than the Ldrd used below for the same
// literal.
Literal<uint64_t> l1(0xcafebeefdeadbaba);
__ Ldr(r0, &l1);
// With the old pool manager, this Ldrd used to force pool emission before
// being generated. Now, 'l1' and 'big_literal' can be reordered in the pool,
// and pool emission is not triggered anymore.
__ Ldrd(r2, r3, &l1);
__ Ldr(r4, MemOperand(r4)); // Load the first 4 characters in r4.
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0xdeadbaba, r0);
ASSERT_EQUAL_32(0xdeadbaba, r2);
ASSERT_EQUAL_32(0xcafebeef, r3);
ASSERT_EQUAL_32(0x78787878, r4);
}
// NOTE: This test has needed modifications for the new pool manager, as it
// was testing a corner case of the previous pool managers. We keep it as
// another testcase.
TEST(emit_reused_load_literal_should_not_rewind) {
// This test checks that we are not conservative when rewinding a load of a
// literal that is already in the literal pool.
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
// This load has a wider range than the Ldrd used below for the same
// literal.
Literal<uint64_t> l1(0xcafebeefdeadbaba);
__ Ldr(r0, &l1);
// Add a large string to the literal pool, but only *after* l1, so the
// Ldrd below should not need to rewind.
const int ldrd_range = masm.IsUsingA32() ? 255 : 1020;
const int string_size = AlignUp(ldrd_range + kMaxInstructionSizeInBytes, 4);
std::string test_string(string_size, 'x');
StringLiteral big_literal(test_string.c_str());
__ Adr(r4, &big_literal);
__ Ldrd(r2, r3, &l1);
// Here we used to check the pool size, which can now be zero as we emit the
// literals in a different order.
// Make sure the pool is emitted.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
__ Ldr(r4, MemOperand(r4)); // Load the first 4 characters in r4.
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0xdeadbaba, r0);
ASSERT_EQUAL_32(0xdeadbaba, r2);
ASSERT_EQUAL_32(0xcafebeef, r3);
ASSERT_EQUAL_32(0x78787878, r4);
}
void EmitReusedLoadLiteralStressTest(InstructionSet isa, bool conditional) {
// This test stresses loading a literal that is already in the literal pool,
// for various positionings on the existing load from that literal. We try to
// exercise cases where the two loads result in similar checkpoints for the
// literal pool.
SETUP();
const int ldrd_range = masm.IsUsingA32() ? 255 : 1020;
const int ldr_range = 4095;
const int nop_size = masm.IsUsingA32() ? 4 : 2;
const int nops = (ldr_range - ldrd_range) / nop_size;
for (int n = nops - 10; n < nops + 10; ++n) {
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
if (conditional) {
__ Mov(r1, 0);
__ Cmp(r1, 0);
}
// Add a large string to the pool, which will stress corner cases with the
// Ldrd below (if the pool is not already emitted due to the Ldr).
const int string_size = AlignUp(ldrd_range + kMaxInstructionSizeInBytes, 4);
std::string test_string(string_size, 'x');
StringLiteral big_literal(test_string.c_str());
__ Ldr(r4, &big_literal);
// This load has a wider range than the Ldrd used below for the same
// literal.
Literal<uint64_t> l1(0xcafebeefdeadbaba);
__ Ldr(r0, &l1);
// Generate nops, in order to bring the checkpoints of the Ldr and Ldrd
// closer.
{
ExactAssemblyScope scope(&masm,
n * nop_size,
ExactAssemblyScope::kExactSize);
for (int i = 0; i < n; ++i) {
__ nop();
}
}
if (conditional) {
__ Ldrd(eq, r2, r3, &l1);
} else {
__ Ldrd(r2, r3, &l1);
}
// Here we used to check that the pool is empty. Since the new pool manager
// allows reordering of literals in the pool, this will not always be the
// case. 'l1' can now be emitted before 'big_literal', allowing the pool to
// be emitted after the ldrd when the number of nops is small enough.
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0xdeadbaba, r0);
ASSERT_EQUAL_32(0xdeadbaba, r2);
ASSERT_EQUAL_32(0xcafebeef, r3);
ASSERT_EQUAL_32(0x78787878, r4);
}
}
TEST(emit_reused_load_literal_stress) {
EmitReusedLoadLiteralStressTest(isa, false /*conditional*/);
}
TEST(emit_reused_conditional_load_literal_stress) {
EmitReusedLoadLiteralStressTest(isa, true /*conditional*/);
}
TEST(test_many_loads_from_same_literal) {
// This test generates multiple loads from the same literal in order to
// test that the delegate recursion limit is appropriate for Ldrd with
// large negative offsets.
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
Literal<uint64_t> l0(0xcafebeefdeadbaba);
__ Ldrd(r0, r1, &l0);
for (int i = 0; i < 10000; ++i) {
__ Add(r2, r2, i);
__ Ldrd(r4, r5, &l0);
}
__ Ldrd(r2, r3, &l0);
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0xdeadbaba, r0);
ASSERT_EQUAL_32(0xcafebeef, r1);
ASSERT_EQUAL_32(0xdeadbaba, r2);
ASSERT_EQUAL_32(0xcafebeef, r3);
ASSERT_EQUAL_32(0xdeadbaba, r4);
ASSERT_EQUAL_32(0xcafebeef, r5);
}
// Make sure calling a macro-assembler instruction will generate literal pools
// if needed.
TEST_T32(literal_pool_generated_by_macro_instruction) {
SETUP();
START();
VIXL_CHECK(test.PoolIsEmpty());
__ Ldrd(r0, r1, 0x1234567890abcdef);
VIXL_CHECK(!test.PoolIsEmpty());
// Generate enough code so that, after the loop, no instruction can be
// generated before we need to generate the literal pool.
// Use `ExactAssemblyScope` and the assembler to generate the code.
int32_t space = test.GetPoolCheckpoint() - masm.GetCursorOffset();
{
ExactAssemblyScope scope(&masm, space, ExactAssemblyScope::kExactSize);
while (space > 0) {
__ nop();
space -= k16BitT32InstructionSizeInBytes;
}
}
// We should not have emitted the literal pool at this point.
VIXL_CHECK(!test.PoolIsEmpty());
VIXL_CHECK(test.GetPoolCheckpoint() == masm.GetCursorOffset());
// Now the pool will need to be generated before we emit anything.
Label check;
__ Bind(&check);
__ Mov(r2, 0x12345678);
// We should have generated 3 wide instructions and 8 bytes of data:
// b.w past_literal_pool
// .bytes 0x1234567890abcdef
// past_literal_pool:
// mov r2, #22136
// movt r2, #4660
VIXL_CHECK(masm.GetSizeOfCodeGeneratedSince(&check) ==
(3 * k32BitT32InstructionSizeInBytes + 8));
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0x12345678, r2);
}
TEST(emit_single_literal) {
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
// Create one literal pool entry.
__ Ldrd(r0, r1, 0x1234567890abcdef);
CHECK_POOL_SIZE(8);
__ Vldr(s0, 1.0);
__ Vldr(d1, 2.0);
__ Vmov(d2, 4.1);
__ Vmov(s8, 8.2);
CHECK_POOL_SIZE(20);
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_FP32(1.0f, s0);
ASSERT_EQUAL_FP64(2.0, d1);
ASSERT_EQUAL_FP64(4.1, d2);
ASSERT_EQUAL_FP32(8.2f, s8);
}
#undef __
#undef __TESTOBJ
#define __ masm->
#define __TESTOBJ test->
void EmitLdrdLiteralTest(MacroAssembler* masm, TestMacroAssembler* test) {
const int ldrd_range = masm->IsUsingA32() ? 255 : 1020;
// We want to emit code up to the maximum literal load range and ensure the
// pool has not been emitted. Compute the limit (end).
ptrdiff_t end = AlignDown(
// Align down the PC to 4 bytes as the instruction does when it's
// executed.
// The PC will be the cursor offset plus the architecture state PC
// offset.
AlignDown(masm->GetBuffer()->GetCursorOffset() +
masm->GetArchitectureStatePCOffset(),
4) +
// Maximum range allowed to access the constant.
ldrd_range -
// Take into account the branch over the pool.
kMaxInstructionSizeInBytes,
// AlignDown to 4 byte as the literals will be 4 byte aligned.
4);
// Create one literal pool entry.
__ Ldrd(r0, r1, 0x1234567890abcdef);
CHECK_POOL_SIZE(8);
int32_t margin = test->GetPoolCheckpoint() - masm->GetCursorOffset();
VIXL_ASSERT(end == test->GetPoolCheckpoint());
{
ExactAssemblyScope scope(masm, margin, ExactAssemblyScope::kExactSize);
// Opening the scope should not have triggered the emission of the literal
// pool.
VIXL_CHECK(!test->PoolIsEmpty());
while (masm->GetCursorOffset() < end) {
__ nop();
}
VIXL_CHECK(masm->GetCursorOffset() == end);
}
// Check that the pool has not been emited along the way.
CHECK_POOL_SIZE(8);
// This extra instruction should trigger an emit of the pool.
__ Nop();
// The pool should have been emitted.
VIXL_CHECK(test->PoolIsEmpty());
}
#undef __
#undef __TESTOBJ
#define __ masm.
#define __TESTOBJ test.
// NOTE: This test has needed modifications for the new pool manager, as it
// was testing a corner case of the previous pool managers. We keep it as
// another testcase.
TEST(emit_literal_rewind) {
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
EmitLdrdLiteralTest(&masm, &test);
const int ldrd_range = masm.IsUsingA32() ? 255 : 1020;
const int string_size = AlignUp(ldrd_range + kMaxInstructionSizeInBytes, 4);
std::string test_string(string_size, 'x');
StringLiteral big_literal(test_string.c_str());
__ Adr(r4, &big_literal);
__ Ldrd(r2, r3, 0xcafebeefdeadbaba);
// With the old pool manager, the adr above would overflow the literal pool
// and force a rewind and pool emission.
// Here we used to check the pool size to confirm that 'big_literal' had
// already been emitted. This does not have to be the case now, as we can
// emit the literals in a different order.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
__ Ldr(r4, MemOperand(r4)); // Load the first 4 characters in r4.
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0xdeadbaba, r2);
ASSERT_EQUAL_32(0xcafebeef, r3);
ASSERT_EQUAL_32(0x78787878, r4);
}
// NOTE: This test has needed modifications for the new pool manager, as it
// was testing a corner case of the previous pool managers. We keep it as
// another testcase.
TEST(emit_literal_conditional_rewind) {
SETUP();
START();
// This test is almost identical to the test above, but the Ldrd instruction
// is conditional and there is a second conditional Ldrd instruction that will
// not be executed.
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
const int ldrd_range = masm.IsUsingA32() ? 255 : 1020;
const int string_size = AlignUp(ldrd_range + kMaxInstructionSizeInBytes, 4);
std::string test_string(string_size, 'x');
StringLiteral big_literal(test_string.c_str());
__ Adr(r2, &big_literal);
__ Mov(r0, 0);
__ Mov(r1, 0);
__ Mov(r3, 1);
__ Cmp(r3, 1);
__ Ldrd(eq, r0, r1, 0xcafebeefdeadbaba);
__ Ldrd(ne, r0, r1, 0xdeadcafebeefbaba);
// With the old pool manager, the adr above would overflow the literal pool
// and force a rewind and pool emission.
// Here we used to check the pool size to confirm that 'big_literal' had
// already been emitted. This does not have to be the case now, as we can
// emit the literals in a different order.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
__ Ldr(r2, MemOperand(r2)); // Load the first 4 characters in r2.
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0xdeadbaba, r0);
ASSERT_EQUAL_32(0xcafebeef, r1);
ASSERT_EQUAL_32(0x78787878, r2);
}
enum LiteralStressTestMode {
kUnconditional,
kConditionalTrue,
kConditionalFalse,
kConditionalBoth
};
// Test loading a literal when the size of the literal pool is close to the
// maximum range of the load, with varying PC values (and alignment, for T32).
// This test is similar to the tests above, with the difference that we allow
// an extra offset to the string size in order to make sure that various pool
// sizes close to the maximum supported offset will produce code that executes
// correctly. As the Ldrd might or might not be emitted before the pool, we do
// not assert on the size of the literal pool in this test.
void EmitLdrdLiteralStressTest(InstructionSet isa,
bool unaligned,
LiteralStressTestMode test_mode) {
SETUP();
for (int offset = -10; offset <= 10; ++offset) {
START();
if (unaligned) {
__ Nop();
VIXL_ASSERT((masm.GetBuffer()->GetCursorOffset() % 4) == 2);
}
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
const int ldrd_range = masm.IsUsingA32() ? 255 : 1020;
const int string_size = ldrd_range + offset;
std::string test_string(string_size - 1, 'x');
StringLiteral big_literal(test_string.c_str());
__ Adr(r2, &big_literal);
__ Mov(r0, 0);
__ Mov(r1, 0);
switch (test_mode) {
case kUnconditional:
__ Ldrd(r0, r1, 0xcafebeefdeadbaba);
break;
case kConditionalTrue:
__ Mov(r0, 0xffffffff);
__ Mov(r1, r0);
__ Mov(r3, 1);
__ Cmp(r3, 1);
__ Ldrd(eq, r0, r1, 0xcafebeefdeadbaba);
break;
case kConditionalFalse:
__ Mov(r0, 0xdeadbaba);
__ Mov(r1, 0xcafebeef);
__ Mov(r3, 1);
__ Cmp(r3, 1);
__ Ldrd(ne, r0, r1, 0xdeadcafebeefbaba);
break;
case kConditionalBoth:
__ Mov(r3, 1);
__ Cmp(r3, 1);
__ Ldrd(eq, r0, r1, 0xcafebeefdeadbaba);
__ Ldrd(ne, r0, r1, 0xdeadcafebeefbaba);
break;
}
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
__ Ldr(r2, MemOperand(r2)); // Load the first 4 characters in r2.
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0xdeadbaba, r0);
ASSERT_EQUAL_32(0xcafebeef, r1);
ASSERT_EQUAL_32(0x78787878, r2);
}
}
TEST(emit_literal_stress) {
EmitLdrdLiteralStressTest(isa, false /*unaligned*/, kUnconditional);
}
TEST_T32(emit_literal_stress_unaligned) {
EmitLdrdLiteralStressTest(isa, true /*unaligned*/, kUnconditional);
}
TEST(emit_literal_conditional_stress) {
EmitLdrdLiteralStressTest(isa, false /*unaligned*/, kConditionalTrue);
EmitLdrdLiteralStressTest(isa, false /*unaligned*/, kConditionalFalse);
EmitLdrdLiteralStressTest(isa, false /*unaligned*/, kConditionalBoth);
}
TEST_T32(emit_literal_conditional_stress_unaligned) {
EmitLdrdLiteralStressTest(isa, true /*unaligned*/, kConditionalTrue);
EmitLdrdLiteralStressTest(isa, true /*unaligned*/, kConditionalFalse);
EmitLdrdLiteralStressTest(isa, true /*unaligned*/, kConditionalBoth);
}
TEST_T32(emit_literal_unaligned) {
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
// Generate a nop to break the 4 bytes alignment.
__ Nop();
EmitLdrdLiteralTest(&masm, &test);
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
}
TEST(literal_multiple_uses) {
SETUP();
START();
Literal<int32_t> lit(42);
__ Ldr(r0, &lit);
CHECK_POOL_SIZE(4);
// Multiple uses of the same literal object should not make the
// pool grow.
__ Ldrb(r1, &lit);
__ Ldrsb(r2, &lit);
__ Ldrh(r3, &lit);
__ Ldrsh(r4, &lit);
CHECK_POOL_SIZE(4);
END();
RUN();
ASSERT_EQUAL_32(42, r0);
ASSERT_EQUAL_32(42, r1);
ASSERT_EQUAL_32(42, r2);
ASSERT_EQUAL_32(42, r3);
ASSERT_EQUAL_32(42, r4);
}
// A test with two loads literal which go out of range at the same time.
TEST_A32(ldr_literal_range_same_time) {
SETUP();
START();
const int ldrd_range = 255;
// We need to take into account the jump over the pool.
const int ldrd_padding = ldrd_range - 2 * kA32InstructionSizeInBytes;
const int ldr_range = 4095;
// We need to take into account the ldrd padding and the ldrd instruction.
const int ldr_padding =
ldr_range - ldrd_padding - 2 * kA32InstructionSizeInBytes;
__ Ldr(r1, 0x12121212);
CHECK_POOL_SIZE(4);
{
int space = AlignDown(ldr_padding, kA32InstructionSizeInBytes);
ExactAssemblyScope scope(&masm, space, ExactAssemblyScope::kExactSize);
int32_t end = masm.GetCursorOffset() + space;
while (masm.GetCursorOffset() < end) {
__ nop();
}
}
__ Ldrd(r2, r3, 0x1234567890abcdef);
CHECK_POOL_SIZE(12);
{
int space = AlignDown(ldrd_padding, kA32InstructionSizeInBytes);
ExactAssemblyScope scope(&masm, space, ExactAssemblyScope::kExactSize);
for (int32_t end = masm.GetCursorOffset() + space;
masm.GetCursorOffset() < end;) {
__ nop();
}
}
CHECK_POOL_SIZE(12);
// This mov will put the two loads literal out of range and will force
// the literal pool emission.
__ Mov(r0, 0);
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
ASSERT_EQUAL_32(0x12121212, r1);
ASSERT_EQUAL_32(0x90abcdef, r2);
ASSERT_EQUAL_32(0x12345678, r3);
}
TEST(ldr_literal_mix_types) {
SETUP();
START();
Literal<uint64_t> l0(0x1234567890abcdef);
Literal<int32_t> l1(0x12345678);
Literal<uint16_t> l2(1234);
Literal<int16_t> l3(-678);
Literal<uint8_t> l4(42);
Literal<int8_t> l5(-12);
__ Ldrd(r0, r1, &l0);
__ Ldr(r2, &l1);
__ Ldrh(r3, &l2);
__ Ldrsh(r4, &l3);
__ Ldrb(r5, &l4);
__ Ldrsb(r6, &l5);
// The pool size does not include padding.
CHECK_POOL_SIZE(18);
END();
RUN();
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0x12345678, r2);
ASSERT_EQUAL_32(1234, r3);
ASSERT_EQUAL_32(-678, r4);
ASSERT_EQUAL_32(42, r5);
ASSERT_EQUAL_32(-12, r6);
}
TEST(ldr_literal_conditional) {
SETUP();
START();
Literal<uint64_t> l0(0x1234567890abcdef);
Literal<uint64_t> l0_not_taken(0x90abcdef12345678);
Literal<int32_t> l1(0x12345678);
Literal<int32_t> l1_not_taken(0x56781234);
Literal<uint16_t> l2(1234);
Literal<uint16_t> l2_not_taken(3412);
Literal<int16_t> l3(-678);
Literal<int16_t> l3_not_taken(678);
Literal<uint8_t> l4(42);
Literal<uint8_t> l4_not_taken(-42);
Literal<int8_t> l5(-12);
Literal<int8_t> l5_not_taken(12);
Literal<float> l6(1.2345f);
Literal<float> l6_not_taken(0.0f);
Literal<double> l7(1.3333);
Literal<double> l7_not_taken(0.0);
// Check that conditionally loading literals of different types works
// correctly for both A32 and T32.
__ Mov(r7, 1);
__ Cmp(r7, 1);
__ Ldrd(eq, r0, r1, &l0);
__ Ldrd(ne, r0, r1, &l0_not_taken);
__ Cmp(r7, 0);
__ Ldr(gt, r2, &l1);
__ Ldr(le, r2, &l1_not_taken);
__ Cmp(r7, 2);
__ Ldrh(lt, r3, &l2);
__ Ldrh(ge, r3, &l2_not_taken);
__ Ldrsh(le, r4, &l3);
__ Ldrsh(gt, r4, &l3_not_taken);
__ Cmp(r7, 1);
__ Ldrb(ge, r5, &l4);
__ Ldrb(lt, r5, &l4_not_taken);
__ Ldrsb(eq, r6, &l5);
__ Ldrsb(ne, r6, &l5_not_taken);
__ Vldr(Condition(eq), s0, &l6);
__ Vldr(Condition(ne), s0, &l6_not_taken);
__ Vldr(Condition(eq), d1, &l7);
__ Vldr(Condition(ne), d1, &l7_not_taken);
END();
RUN();
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0x12345678, r2);
ASSERT_EQUAL_32(1234, r3);
ASSERT_EQUAL_32(-678, r4);
ASSERT_EQUAL_32(42, r5);
ASSERT_EQUAL_32(-12, r6);
ASSERT_EQUAL_FP32(1.2345f, s0);
ASSERT_EQUAL_FP64(1.3333, d1);
}
struct LdrLiteralRangeTest {
void (MacroAssembler::*instruction)(Register, RawLiteral*);
Register result_reg;
int a32_range;
int t32_range;
uint32_t literal_value;
uint32_t test_value;
};
const LdrLiteralRangeTest kLdrLiteralRangeTestData[] =
{{&MacroAssembler::Ldr, r1, 4095, 4095, 0x12345678, 0x12345678},
{&MacroAssembler::Ldrh, r2, 255, 4095, 0xabcdefff, 0x0000efff},
{&MacroAssembler::Ldrsh, r3, 255, 4095, 0x00008765, 0xffff8765},
{&MacroAssembler::Ldrb, r4, 4095, 4095, 0x12345678, 0x00000078},
{&MacroAssembler::Ldrsb, r5, 255, 4095, 0x00000087, 0xffffff87}};
void GenerateLdrLiteralTriggerPoolEmission(InstructionSet isa,
bool unaligned_ldr) {
SETUP();
for (size_t i = 0; i < ARRAY_SIZE(kLdrLiteralRangeTestData); ++i) {
const LdrLiteralRangeTest& test_case = kLdrLiteralRangeTestData[i];
START();
if (unaligned_ldr) {
// Generate a nop to break the 4-byte alignment.
__ Nop();
VIXL_ASSERT((masm.GetBuffer()->GetCursorOffset() % 4) == 2);
}
__ Ldr(r6, 0x12345678);
CHECK_POOL_SIZE(4);
// TODO: The MacroAssembler currently checks for more space than required
// when emitting macro instructions, triggering emission of the pool before
// absolutely required. For now we keep a buffer. Fix this test when the
// MacroAssembler becomes precise again.
int masm_check_margin = 10 * kMaxInstructionSizeInBytes;
int expected_pool_size = 4;
while ((test.GetPoolCheckpoint() - masm.GetCursorOffset() -
masm_check_margin) >=
static_cast<int32_t>(kMaxInstructionSizeInBytes)) {
__ Ldr(r7, 0x90abcdef);
// Each ldr instruction will force a new literal value to be added
// to the pool. Check that the literal pool grows accordingly.
expected_pool_size += 4;
CHECK_POOL_SIZE(expected_pool_size);
}
int space = test.GetPoolCheckpoint() - masm.GetCursorOffset();
int end = masm.GetCursorOffset() + space;
{
// Generate nops precisely to fill the buffer.
ExactAssemblyScope accurate_scope(&masm, space); // This should not
// trigger emission of
// the pool.
VIXL_CHECK(!test.PoolIsEmpty());
while (masm.GetCursorOffset() < end) {
__ nop();
}
}
// This ldr will force the literal pool to be emitted before emitting
// the load and will create a new pool for the new literal used by this ldr.
VIXL_CHECK(!test.PoolIsEmpty());
Literal<uint32_t> literal(test_case.literal_value);
(masm.*test_case.instruction)(test_case.result_reg, &literal);
CHECK_POOL_SIZE(4);
END();
RUN();
ASSERT_EQUAL_32(0x12345678, r6);
ASSERT_EQUAL_32(0x90abcdef, r7);
ASSERT_EQUAL_32(test_case.test_value, test_case.result_reg);
}
}
TEST(ldr_literal_trigger_pool_emission) {
GenerateLdrLiteralTriggerPoolEmission(isa, false);
}
TEST_T32(ldr_literal_trigger_pool_emission_unaligned) {
GenerateLdrLiteralTriggerPoolEmission(isa, true);
}
void GenerateLdrLiteralRangeTest(InstructionSet isa, bool unaligned_ldr) {
SETUP();
for (size_t i = 0; i < ARRAY_SIZE(kLdrLiteralRangeTestData); ++i) {
const LdrLiteralRangeTest& test_case = kLdrLiteralRangeTestData[i];
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
if (unaligned_ldr) {
// Generate a nop to break the 4-byte alignment.
__ Nop();
VIXL_ASSERT((masm.GetBuffer()->GetCursorOffset() % 4) == 2);
}
Literal<uint32_t> literal(test_case.literal_value);
(masm.*test_case.instruction)(test_case.result_reg, &literal);
CHECK_POOL_SIZE(4);
// Generate enough instruction so that we go out of range for the load
// literal we just emitted.
ptrdiff_t end =
masm.GetBuffer()->GetCursorOffset() +
((masm.IsUsingA32()) ? test_case.a32_range : test_case.t32_range);
while (masm.GetBuffer()->GetCursorOffset() < end) {
__ Mov(r0, 0);
}
// The literal pool should have been emitted now.
VIXL_CHECK(literal.IsBound());
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
ASSERT_EQUAL_32(test_case.test_value, test_case.result_reg);
}
}
TEST(ldr_literal_range) { GenerateLdrLiteralRangeTest(isa, false); }
TEST_T32(ldr_literal_range_unaligned) {
GenerateLdrLiteralRangeTest(isa, true);
}
TEST(string_literal) {
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
StringLiteral hello_string("hello");
__ Ldrb(r1, &hello_string);
__ Adr(r0, &hello_string);
__ Ldrb(r2, MemOperand(r0));
END();
RUN();
ASSERT_EQUAL_32('h', r1);
ASSERT_EQUAL_32('h', r2);
}
TEST(custom_literal_in_pool) {
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
Literal<uint32_t> l0(static_cast<uint32_t>(0x12345678));
__ Ldr(r0, &l0);
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
__ Ldr(r1, &l0);
VIXL_CHECK(test.PoolIsEmpty());
Literal<uint64_t> cafebeefdeadbaba(0xcafebeefdeadbaba);
__ Ldrd(r8, r9, &cafebeefdeadbaba);
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
__ Ldrd(r2, r3, &cafebeefdeadbaba);
VIXL_CHECK(test.PoolIsEmpty());
Literal<uint32_t> l1(0x09abcdef);
__ Adr(r4, &l1);
__ Ldr(r4, MemOperand(r4));
masm.EmitLiteralPool();
__ Adr(r5, &l1);
__ Ldr(r5, MemOperand(r5));
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x12345678, r0);
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0xdeadbaba, r2);
ASSERT_EQUAL_32(0xcafebeef, r3);
ASSERT_EQUAL_32(0xdeadbaba, r8);
ASSERT_EQUAL_32(0xcafebeef, r9);
ASSERT_EQUAL_32(0x09abcdef, r4);
ASSERT_EQUAL_32(0x09abcdef, r5);
}
TEST(custom_literal_place) {
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
Literal<uint64_t> l0(0xcafebeefdeadbaba, RawLiteral::kManuallyPlaced);
Literal<int32_t> l1(0x12345678, RawLiteral::kManuallyPlaced);
Literal<uint16_t> l2(4567, RawLiteral::kManuallyPlaced);
Literal<int16_t> l3(-4567, RawLiteral::kManuallyPlaced);
Literal<uint8_t> l4(123, RawLiteral::kManuallyPlaced);
Literal<int8_t> l5(-123, RawLiteral::kManuallyPlaced);
__ Ldrd(r0, r1, &l0);
__ Ldr(r2, &l1);
__ Ldrh(r3, &l2);
__ Ldrsh(r4, &l3);
__ Ldrb(r5, &l4);
__ Ldrsb(r6, &l5);
VIXL_CHECK(test.PoolIsEmpty());
// Manually generate a literal pool.
Label after_pool;
__ B(&after_pool);
__ Place(&l0);
__ Place(&l1);
__ Place(&l2);
__ Place(&l3);
__ Place(&l4);
__ Place(&l5);
__ Bind(&after_pool);
{
UseScratchRegisterScope temps(&masm);
Register temp = temps.Acquire();
VIXL_CHECK(temp.Is(r12));
__ Ldrd(r8, r9, &l0);
__ Ldr(r7, &l1);
__ Ldrh(r10, &l2);
__ Ldrsh(r11, &l3);
__ Ldrb(temp, &l4);
// We don't use any function call so we can use lr as an extra register.
__ Ldrsb(lr, &l5);
}
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0xdeadbaba, r0);
ASSERT_EQUAL_32(0xcafebeef, r1);
ASSERT_EQUAL_32(0x12345678, r2);
ASSERT_EQUAL_32(4567, r3);
ASSERT_EQUAL_32(-4567, r4);
ASSERT_EQUAL_32(123, r5);
ASSERT_EQUAL_32(-123, r6);
ASSERT_EQUAL_32(0xdeadbaba, r8);
ASSERT_EQUAL_32(0xcafebeef, r9);
ASSERT_EQUAL_32(0x12345678, r7);
ASSERT_EQUAL_32(4567, r10);
ASSERT_EQUAL_32(-4567, r11);
ASSERT_EQUAL_32(123, r12);
ASSERT_EQUAL_32(-123, lr);
}
TEST(custom_literal_place_shared) {
SETUP();
for (size_t i = 0; i < ARRAY_SIZE(kLdrLiteralRangeTestData); ++i) {
const LdrLiteralRangeTest& test_case = kLdrLiteralRangeTestData[i];
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
Literal<uint32_t> before(test_case.literal_value,
RawLiteral::kManuallyPlaced);
Literal<uint32_t> after(test_case.literal_value,
RawLiteral::kManuallyPlaced);
VIXL_CHECK(!before.IsBound());
VIXL_CHECK(!after.IsBound());
// Manually generate a pool.
Label end_of_pool_before;
__ B(&end_of_pool_before);
__ Place(&before);
__ Bind(&end_of_pool_before);
VIXL_CHECK(test.PoolIsEmpty());
VIXL_CHECK(before.IsBound());
VIXL_CHECK(!after.IsBound());
// Load the entries several times to test that literals can be shared.
for (int i = 0; i < 20; i++) {
(masm.*test_case.instruction)(r0, &before);
(masm.*test_case.instruction)(r1, &after);
}
VIXL_CHECK(test.PoolIsEmpty());
VIXL_CHECK(before.IsBound());
VIXL_CHECK(!after.IsBound());
// Manually generate a pool.
Label end_of_pool_after;
__ B(&end_of_pool_after);
__ Place(&after);
__ Bind(&end_of_pool_after);
VIXL_CHECK(test.PoolIsEmpty());
VIXL_CHECK(before.IsBound());
VIXL_CHECK(after.IsBound());
END();
RUN();
ASSERT_EQUAL_32(test_case.test_value, r0);
ASSERT_EQUAL_32(test_case.test_value, r1);
}
}
TEST(custom_literal_place_range) {
SETUP();
for (size_t i = 0; i < ARRAY_SIZE(kLdrLiteralRangeTestData); ++i) {
const LdrLiteralRangeTest& test_case = kLdrLiteralRangeTestData[i];
const int nop_size = masm.IsUsingA32() ? kA32InstructionSizeInBytes
: k16BitT32InstructionSizeInBytes;
const int range =
masm.IsUsingA32() ? test_case.a32_range : test_case.t32_range;
// On T32 the PC will be 4-byte aligned to compute the range. The
// MacroAssembler might also need to align the code buffer before emitting
// the literal when placing it. We keep a margin to account for this.
const int margin = masm.IsUsingT32() ? 4 : 0;
// Take PC offset into account and make sure the literal is in the range.
const int padding_before =
range - masm.GetArchitectureStatePCOffset() - sizeof(uint32_t) - margin;
// The margin computation below is correct because the ranges are not
// 4-byte aligned. Otherwise this test would insert the exact number of
// instructions to cover the range and the literal would end up being
// placed outside the range.
VIXL_ASSERT((range % 4) != 0);
// The range is extended by the PC offset but we need to consider the ldr
// instruction itself and the branch over the pool.
const int padding_after = range + masm.GetArchitectureStatePCOffset() -
(2 * kMaxInstructionSizeInBytes) - margin;
START();
Literal<uint32_t> before(test_case.literal_value,
RawLiteral::kManuallyPlaced);
Literal<uint32_t> after(test_case.literal_value,
RawLiteral::kManuallyPlaced);
Label test_start;
__ B(&test_start);
__ Place(&before);
{
int space = AlignDown(padding_before, nop_size);
ExactAssemblyScope scope(&masm, space, ExactAssemblyScope::kExactSize);
for (int32_t end = masm.GetCursorOffset() + space;
masm.GetCursorOffset() < end;) {
__ nop();
}
}
__ Bind(&test_start);
(masm.*test_case.instruction)(r0, &before);
(masm.*test_case.instruction)(r1, &after);
{
int space = AlignDown(padding_after, nop_size);
ExactAssemblyScope scope(&masm, space, ExactAssemblyScope::kExactSize);
for (int32_t end = masm.GetCursorOffset() + space;
masm.GetCursorOffset() < end;) {
__ nop();
}
}
Label after_pool;
__ B(&after_pool);
__ Place(&after);
__ Bind(&after_pool);
END();
RUN();
ASSERT_EQUAL_32(test_case.test_value, r0);
ASSERT_EQUAL_32(test_case.test_value, r1);
}
}
TEST(emit_big_pool) {
SETUP();
START();
// Make sure the pool is empty.
VIXL_CHECK(test.PoolIsEmpty());
Label start;
__ Bind(&start);
for (int i = 1000; i > 0; --i) {
__ Ldr(r0, i);
}
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&start) == 4000);
CHECK_POOL_SIZE(4000);
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(1, r0);
}
TEST_T32(too_far_cbz) {
SETUP();
START();
Label start;
Label end;
Label exit;
__ Mov(r0, 0);
__ B(&start);
__ Bind(&end);
__ Mov(r0, 1);
__ B(&exit);
__ Bind(&start);
// Cbz is only defined for forward jump. Check that it will work (substituted
// by Cbnz/B).
__ Cbz(r0, &end);
__ Bind(&exit);
END();
RUN();
ASSERT_EQUAL_32(1, r0);
}
TEST_T32(close_cbz) {
SETUP();
START();
Label first;
Label second;
__ Mov(r0, 0);
__ Mov(r1, 0);
__ Mov(r2, 0);
__ Cbz(r0, &first);
__ Bind(&first);
__ Mov(r1, 1);
__ Cbnz(r0, &second);
__ Bind(&second);
__ Mov(r2, 2);
END();
RUN();
ASSERT_EQUAL_32(0, r0);
ASSERT_EQUAL_32(1, r1);
ASSERT_EQUAL_32(2, r2);
}
TEST_T32(close_cbz2) {
SETUP();
START();
Label first;
Label second;
__ Mov(r0, 0);
__ Mov(r1, 0);
__ Mov(r2, 0);
__ Cmp(r0, 0);
__ B(ne, &first);
__ B(gt, &second);
__ Cbz(r0, &first);
__ Bind(&first);
__ Mov(r1, 1);
__ Cbnz(r0, &second);
__ Bind(&second);
__ Mov(r2, 2);
END();
RUN();
ASSERT_EQUAL_32(0, r0);
ASSERT_EQUAL_32(1, r1);
ASSERT_EQUAL_32(2, r2);
}
TEST_T32(not_close_cbz) {
SETUP();
START();
Label first;
Label second;
__ Cbz(r0, &first);
__ B(ne, &first);
__ Bind(&first);
__ Cbnz(r0, &second);
__ B(gt, &second);
__ Bind(&second);
END();
RUN();
}
TEST_T32(veneers) {
SETUP();
START();
Label zero;
Label exit;
__ Mov(r0, 0);
// Create one literal pool entry.
__ Ldr(r1, 0x12345678);
CHECK_POOL_SIZE(4);
__ Cbz(r0, &zero);
__ Mov(r0, 1);
__ B(&exit);
for (int i = 32; i > 0; i--) {
__ Mov(r1, 0);
}
// Assert that the pool contains only the two veneers.
const int kVeneerSize = 4;
CHECK_POOL_SIZE(2 * kVeneerSize);
__ Bind(&zero);
__ Mov(r0, 2);
__ Bind(&exit);
END();
RUN();
ASSERT_EQUAL_32(2, r0);
ASSERT_EQUAL_32(0x12345678, r1);
}
// This test checks that veneers are sorted. If not, the test failed as the
// veneer for "exit" is emitted before the veneer for "zero" and the "zero"
// veneer is out of range for Cbz.
TEST_T32(veneers_labels_sort) {
SETUP();
START();
Label start;
Label zero;
Label exit;
__ Movs(r0, 0);
__ B(ne, &exit);
__ B(&start);
for (int i = 1048400; i > 0; i -= 4) {
__ Mov(r1, 0);
}
__ Bind(&start);
__ Cbz(r0, &zero);
__ Mov(r0, 1);
__ B(&exit);
for (int i = 32; i > 0; i--) {
__ Mov(r1, 0);
}
__ Bind(&zero);
__ Mov(r0, 2);
__ Bind(&exit);
END();
RUN();
ASSERT_EQUAL_32(2, r0);
}
// Check that a label bound within the assembler is effectively removed from
// the veneer pool.
TEST_T32(veneer_bind) {
SETUP();
START();
Label target;
__ Cbz(r0, &target);
__ Nop();
{
// Bind the target label using the `Assembler`.
ExactAssemblyScope scope(&masm,
kMaxInstructionSizeInBytes,
ExactAssemblyScope::kMaximumSize);
__ bind(&target);
__ nop();
}
VIXL_CHECK(target.IsBound());
VIXL_CHECK(test.PoolIsEmpty());
END();
}
// Check that the veneer pool is correctly emitted even if we do enough narrow
// branches before a cbz so that the cbz needs its veneer emitted first in the
// pool in order to work.
TEST_T32(b_narrow_and_cbz_sort) {
SETUP();
START();
const int kLabelsCount = 40;
const int kNops = 30;
Label b_labels[kLabelsCount];
Label cbz_label;
__ Nop();
__ Mov(r0, 0);
__ Cmp(r0, 0);
for (int i = 0; i < kLabelsCount; ++i) {
__ B(ne, &b_labels[i], kNear);
}
{
ExactAssemblyScope scope(&masm,
k16BitT32InstructionSizeInBytes * kNops,
ExactAssemblyScope::kExactSize);
for (int i = 0; i < kNops; i++) {
__ nop();
}
}
// The pool should not be emitted here.
__ Cbz(r0, &cbz_label);
// Force pool emission. If the labels are not sorted, the cbz will be out
// of range.
int32_t end = test.GetPoolCheckpoint();
int32_t margin = end - masm.GetCursorOffset();
{
ExactAssemblyScope scope(&masm, margin, ExactAssemblyScope::kExactSize);
while (masm.GetCursorOffset() < end) {
__ nop();
}
}
__ Mov(r0, 1);
for (int i = 0; i < kLabelsCount; ++i) {
__ Bind(&b_labels[i]);
}
__ Bind(&cbz_label);
END();
RUN();
ASSERT_EQUAL_32(0, r0);
}
TEST_T32(b_narrow_and_cbz_sort_2) {
SETUP();
START();
const int kLabelsCount = 40;
const int kNops = 30;
Label b_labels[kLabelsCount];
Label cbz_label;
__ Mov(r0, 0);
__ Cmp(r0, 0);
for (int i = 0; i < kLabelsCount; ++i) {
__ B(ne, &b_labels[i], kNear);
}
{
ExactAssemblyScope scope(&masm,
k16BitT32InstructionSizeInBytes * kNops,
ExactAssemblyScope::kExactSize);
for (int i = 0; i < kNops; i++) {
__ nop();
}
}
// The pool should not be emitted here.
__ Cbz(r0, &cbz_label);
// Force pool emission. If the labels are not sorted, the cbz will be out
// of range.
int32_t end = test.GetPoolCheckpoint();
while (masm.GetCursorOffset() < end) __ Nop();
__ Mov(r0, 1);
for (int i = 0; i < kLabelsCount; ++i) {
__ Bind(&b_labels[i]);
}
__ Bind(&cbz_label);
END();
RUN();
ASSERT_EQUAL_32(0, r0);
}
TEST_T32(long_branch) {
SETUP();
START();
for (int label_count = 128; label_count < 2048; label_count *= 2) {
Label* l = new Label[label_count];
for (int i = 0; i < label_count; i++) {
__ B(&l[i]);
}
for (int i = 0; i < label_count; i++) {
__ B(ne, &l[i]);
}
for (int i = 0; i < 261625; i++) {
__ Clz(r0, r0);
}
for (int i = label_count - 1; i >= 0; i--) {
__ Bind(&l[i]);
__ Nop();
}
delete[] l;
}
masm.FinalizeCode();
END();
RUN();
}
TEST_T32(unaligned_branch_after_literal) {
SETUP();
START();
// This test manually places a 32-bit literal after a 16-bit branch
// which branches over the literal to an unaligned PC.
Literal<int32_t> l0(0x01234567, RawLiteral::kManuallyPlaced);
__ Ldr(r0, &l0);
VIXL_CHECK(test.PoolIsEmpty());
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
// Manually generate a literal pool.
{
Label after_pool;
ExactAssemblyScope scope(&masm,
k16BitT32InstructionSizeInBytes + sizeof(int32_t),
CodeBufferCheckScope::kMaximumSize);
__ b(Narrow, &after_pool);
__ place(&l0);
VIXL_ASSERT((masm.GetBuffer()->GetCursorOffset() % 4) == 2);
__ bind(&after_pool);
}
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
// Check that the literal was loaded correctly.
ASSERT_EQUAL_32(0x01234567, r0);
}
// This test check that we can update a Literal after usage.
TEST(literal_update) {
SETUP();
START();
Label exit;
Literal<uint32_t>* a32 =
new Literal<uint32_t>(0xabcdef01, RawLiteral::kDeletedOnPoolDestruction);
Literal<uint64_t>* a64 =
new Literal<uint64_t>(UINT64_C(0xabcdef01abcdef01),
RawLiteral::kDeletedOnPoolDestruction);
__ Ldr(r0, a32);
__ Ldrd(r2, r3, a64);
__ EmitLiteralPool();
Literal<uint32_t>* b32 =
new Literal<uint32_t>(0x10fedcba, RawLiteral::kDeletedOnPoolDestruction);
Literal<uint64_t>* b64 =
new Literal<uint64_t>(UINT64_C(0x10fedcba10fedcba),
RawLiteral::kDeletedOnPoolDestruction);
__ Ldr(r1, b32);
__ Ldrd(r4, r5, b64);
// Update literals' values. "a32" and "a64" are already emitted. "b32" and
// "b64" will only be emitted when "END()" will be called.
a32->UpdateValue(0x12345678, masm.GetBuffer());
a64->UpdateValue(UINT64_C(0x13579bdf02468ace), masm.GetBuffer());
b32->UpdateValue(0x87654321, masm.GetBuffer());
b64->UpdateValue(UINT64_C(0x1032547698badcfe), masm.GetBuffer());
END();
RUN();
ASSERT_EQUAL_32(0x12345678, r0);
ASSERT_EQUAL_32(0x87654321, r1);
ASSERT_EQUAL_32(0x02468ace, r2);
ASSERT_EQUAL_32(0x13579bdf, r3);
ASSERT_EQUAL_32(0x98badcfe, r4);
ASSERT_EQUAL_32(0x10325476, r5);
}
TEST(claim_peek_poke) {
SETUP();
START();
Label start;
__ Bind(&start);
__ Claim(0);
__ Drop(0);
VIXL_CHECK((masm.GetCursorOffset() - start.GetLocation()) == 0);
__ Claim(32);
__ Ldr(r0, 0xcafe0000);
__ Ldr(r1, 0xcafe0001);
__ Ldr(r2, 0xcafe0002);
__ Poke(r0, 0);
__ Poke(r1, 4);
__ Poke(r2, 8);
__ Peek(r2, 0);
__ Peek(r0, 4);
__ Peek(r1, 8);
__ Drop(32);
END();
RUN();
ASSERT_EQUAL_32(0xcafe0001, r0);
ASSERT_EQUAL_32(0xcafe0002, r1);
ASSERT_EQUAL_32(0xcafe0000, r2);
}
TEST(msr_i) {
SETUP();
START();
__ Mov(r0, 0xdead);
__ Mov(r1, 0xdead);
__ Mov(r2, 0xdead);
__ Mov(r3, 0xb);
__ Msr(APSR_nzcvqg, 0);
__ Mrs(r0, APSR);
__ Msr(APSR_nzcvqg, 0xffffffff);
__ Mrs(r1, APSR);
// Only modify nzcvq => keep previous g.
__ Lsl(r4, r3, 28);
__ Msr(APSR_nzcvq, r4);
__ Mrs(r2, APSR);
END();
RUN();
ASSERT_EQUAL_32(0x10, r0);
ASSERT_EQUAL_32(0xf80f0010, r1);
ASSERT_EQUAL_32(0xb00f0010, r2);
}
TEST(vcmp_s) {
SETUP();
START();
__ Vmov(s0, 1.0);
__ Vmov(s1, 2.0);
__ Vmov(s2, 0.0);
__ Vcmp(F32, s0, s1);
__ Vmrs(RegisterOrAPSR_nzcv(r0.GetCode()), FPSCR);
__ Vcmp(F32, s0, 0.0f);
__ Vmrs(RegisterOrAPSR_nzcv(r1.GetCode()), FPSCR);
__ Vcmp(F32, s2, 0.0f);
__ Vmrs(RegisterOrAPSR_nzcv(r2.GetCode()), FPSCR);
END();
RUN();
// N is for less than.
ASSERT_EQUAL_32(NFlag, r0);
// C is for greater than.
ASSERT_EQUAL_32(CFlag, r1);
// ZC is for equal.
ASSERT_EQUAL_32(ZCFlag, r2);
}
TEST(vcmp_d) {
SETUP();
START();
__ Vmov(d0, 1.0);
__ Vmov(d1, 2.0);
__ Vmov(d2, 0.0);
__ Vcmp(F64, d0, d1);
__ Vmrs(RegisterOrAPSR_nzcv(r0.GetCode()), FPSCR);
__ Vcmp(F64, d0, 0.0);
__ Vmrs(RegisterOrAPSR_nzcv(r1.GetCode()), FPSCR);
__ Vcmp(F64, d2, 0.0);
__ Vmrs(RegisterOrAPSR_nzcv(r2.GetCode()), FPSCR);
END();
RUN();
// N is for less than.
ASSERT_EQUAL_32(NFlag, r0);
// C is for greater than.
ASSERT_EQUAL_32(CFlag, r1);
// ZC is for equal.
ASSERT_EQUAL_32(ZCFlag, r2);
}
TEST(vcmpe_s) {
SETUP();
START();
__ Vmov(s0, 1.0);
__ Vmov(s1, 2.0);
__ Vmov(s2, 0.0);
__ Vcmpe(F32, s0, s1);
__ Vmrs(RegisterOrAPSR_nzcv(r0.GetCode()), FPSCR);
__ Vcmpe(F32, s0, 0.0f);
__ Vmrs(RegisterOrAPSR_nzcv(r1.GetCode()), FPSCR);
__ Vcmpe(F32, s2, 0.0f);
__ Vmrs(RegisterOrAPSR_nzcv(r2.GetCode()), FPSCR);
END();
RUN();
// N is for less than.
ASSERT_EQUAL_32(NFlag, r0);
// C is for greater than.
ASSERT_EQUAL_32(CFlag, r1);
// ZC is for equal.
ASSERT_EQUAL_32(ZCFlag, r2);
}
TEST(vcmpe_d) {
SETUP();
START();
__ Vmov(d0, 1.0);
__ Vmov(d1, 2.0);
__ Vmov(d2, 0.0);
__ Vcmpe(F64, d0, d1);
__ Vmrs(RegisterOrAPSR_nzcv(r0.GetCode()), FPSCR);
__ Vcmpe(F64, d0, 0.0);
__ Vmrs(RegisterOrAPSR_nzcv(r1.GetCode()), FPSCR);
__ Vcmpe(F64, d2, 0.0);
__ Vmrs(RegisterOrAPSR_nzcv(r2.GetCode()), FPSCR);
END();
RUN();
// N is for less than.
ASSERT_EQUAL_32(NFlag, r0);
// C is for greater than.
ASSERT_EQUAL_32(CFlag, r1);
// ZC is for equal.
ASSERT_EQUAL_32(ZCFlag, r2);
}
TEST(vmrs_vmsr) {
SETUP();
START();
// Move some value to FPSCR and get them back to test vmsr/vmrs instructions.
__ Mov(r0, 0x2a000000);
__ Vmsr(FPSCR, r0);
__ Vmrs(RegisterOrAPSR_nzcv(r1.GetCode()), FPSCR);
__ Mov(r0, 0x5a000000);
__ Vmsr(FPSCR, r0);
__ Vmrs(RegisterOrAPSR_nzcv(r2.GetCode()), FPSCR);
// Move to APSR_nzcv.
__ Vmrs(RegisterOrAPSR_nzcv(pc.GetCode()), FPSCR);
__ Mrs(r3, APSR);
__ And(r3, r3, 0xf0000000);
END();
RUN();
ASSERT_EQUAL_32(0x2a000000, r1);
ASSERT_EQUAL_32(0x5a000000, r2);
ASSERT_EQUAL_32(0x50000000, r3);
}
TEST(printf) {
SETUP();
START();
__ Mov(r0, 0xb00e0000);
__ Msr(APSR_nzcvqg, r0);
__ Mov(r0, sp);
__ Printf("sp=%x\n", r0);
// __ Printf("Hello world!\n");
__ Mov(r0, 0x1234);
__ Mov(r1, 0x5678);
StringLiteral literal("extra string");
__ Adr(r2, &literal);
__ Mov(r3, 5);
__ Mov(r4, 0xdead4444);
__ Mov(r5, 0xdead5555);
__ Mov(r6, 0xdead6666);
__ Mov(r7, 0xdead7777);
__ Mov(r8, 0xdead8888);
__ Mov(r9, 0xdead9999);
__ Mov(r10, 0xdeadaaaa);
__ Mov(r11, 0xdeadbbbb);
__ Vldr(d0, 1.2345);
__ Vldr(d1, 2.9876);
__ Vldr(s4, 1.3333);
__ Vldr(s5, 3.21);
__ Vldr(d3, 3.333);
__ Vldr(d4, 4.444);
__ Vldr(d5, 5.555);
__ Vldr(d6, 6.666);
__ Vldr(d7, 7.777);
__ Vldr(d8, 8.888);
__ Vldr(d9, 9.999);
__ Vldr(d10, 10.000);
__ Vldr(d11, 11.111);
__ Vldr(d12, 12.222);
__ Vldr(d13, 13.333);
__ Vldr(d14, 14.444);
__ Vldr(d15, 15.555);
__ Vldr(d16, 16.666);
__ Vldr(d17, 17.777);
__ Vldr(d18, 18.888);
__ Vldr(d19, 19.999);
__ Vldr(d20, 20.000);
__ Vldr(d21, 21.111);
__ Vldr(d22, 22.222);
__ Vldr(d23, 23.333);
__ Vldr(d24, 24.444);
__ Vldr(d25, 25.555);
__ Vldr(d26, 26.666);
__ Vldr(d27, 27.777);
__ Vldr(d28, 28.888);
__ Vldr(d29, 29.999);
__ Vldr(d30, 30.000);
__ Vldr(d31, 31.111);
{
UseScratchRegisterScope temps(&masm);
// For effective use as an inspection tool, Printf must work without any
// scratch registers.
VIXL_CHECK(r12.Is(temps.Acquire()));
__ Mov(r12, 0xdeadcccc);
VIXL_CHECK(masm.GetScratchRegisterList()->IsEmpty());
__ Printf("%% r0=%x r1=%x str=<%.*s>\n", r0, r1, r3, r2);
__ Printf("r0=%d r1=%d str=<%s>\n", r0, r1, r2);
__ Printf("d0=%g\n", d0);
__ Printf("s4=%g\n", s4);
__ Printf("d0=%g d1=%g s4=%g s5=%g\n", d0, d1, s4, s5);
__ Printf("d0=%g r0=%x s4=%g r1=%x\n", d0, r0, s4, r1);
__ Printf("r0=%x d0=%g r1=%x s4=%g\n", r0, d0, r1, s4);
__ Mov(r0, sp);
__ Printf("sp=%x\n", r0);
__ Mrs(r0, APSR);
// Only keep R/W fields.
__ Mov(r2, 0xf80f0200);
__ And(r0, r0, r2);
}
END();
RUN();
ASSERT_EQUAL_32(0xb00e0000, r0);
ASSERT_EQUAL_32(0x5678, r1);
ASSERT_EQUAL_32(5, r3);
ASSERT_EQUAL_32(0xdead4444, r4);
ASSERT_EQUAL_32(0xdead5555, r5);
ASSERT_EQUAL_32(0xdead6666, r6);
ASSERT_EQUAL_32(0xdead7777, r7);
ASSERT_EQUAL_32(0xdead8888, r8);
ASSERT_EQUAL_32(0xdead9999, r9);
ASSERT_EQUAL_32(0xdeadaaaa, r10);
ASSERT_EQUAL_32(0xdeadbbbb, r11);
ASSERT_EQUAL_32(0xdeadcccc, r12);
ASSERT_EQUAL_FP64(1.2345, d0);
ASSERT_EQUAL_FP64(2.9876, d1);
ASSERT_EQUAL_FP32(1.3333, s4);
ASSERT_EQUAL_FP32(3.21, s5);
ASSERT_EQUAL_FP64(4.444, d4);
ASSERT_EQUAL_FP64(5.555, d5);
ASSERT_EQUAL_FP64(6.666, d6);
ASSERT_EQUAL_FP64(7.777, d7);
ASSERT_EQUAL_FP64(8.888, d8);
ASSERT_EQUAL_FP64(9.999, d9);
ASSERT_EQUAL_FP64(10.000, d10);
ASSERT_EQUAL_FP64(11.111, d11);
ASSERT_EQUAL_FP64(12.222, d12);
ASSERT_EQUAL_FP64(13.333, d13);
ASSERT_EQUAL_FP64(14.444, d14);
ASSERT_EQUAL_FP64(15.555, d15);
ASSERT_EQUAL_FP64(16.666, d16);
ASSERT_EQUAL_FP64(17.777, d17);
ASSERT_EQUAL_FP64(18.888, d18);
ASSERT_EQUAL_FP64(19.999, d19);
ASSERT_EQUAL_FP64(20.000, d20);
ASSERT_EQUAL_FP64(21.111, d21);
ASSERT_EQUAL_FP64(22.222, d22);
ASSERT_EQUAL_FP64(23.333, d23);
ASSERT_EQUAL_FP64(24.444, d24);
ASSERT_EQUAL_FP64(25.555, d25);
ASSERT_EQUAL_FP64(26.666, d26);
ASSERT_EQUAL_FP64(27.777, d27);
ASSERT_EQUAL_FP64(28.888, d28);
ASSERT_EQUAL_FP64(29.999, d29);
ASSERT_EQUAL_FP64(30.000, d30);
ASSERT_EQUAL_FP64(31.111, d31);
}
TEST(printf2) {
SETUP();
START();
__ Mov(r0, 0x1234);
__ Mov(r1, 0x5678);
__ Vldr(d0, 1.2345);
__ Vldr(s2, 2.9876);
__ Printf("d0=%g d1=%g r0=%x r1=%x\n", d0, s2, r0, r1);
END();
RUN();
}
template <typename T>
void CheckInstructionSetA32(const T& assm) {
VIXL_CHECK(assm.IsUsingA32());
VIXL_CHECK(!assm.IsUsingT32());
VIXL_CHECK(assm.GetInstructionSetInUse() == A32);
}
template <typename T>
void CheckInstructionSetT32(const T& assm) {
VIXL_CHECK(assm.IsUsingT32());
VIXL_CHECK(!assm.IsUsingA32());
VIXL_CHECK(assm.GetInstructionSetInUse() == T32);
}
TEST_NOASM(set_isa_constructors) {
byte buffer[1024];
#ifndef VIXL_INCLUDE_TARGET_T32_ONLY
// A32 by default.
CheckInstructionSetA32(Assembler());
CheckInstructionSetA32(Assembler(1024));
CheckInstructionSetA32(Assembler(buffer, sizeof(buffer)));
CheckInstructionSetA32(MacroAssembler());
CheckInstructionSetA32(MacroAssembler(1024));
CheckInstructionSetA32(MacroAssembler(buffer, sizeof(buffer)));
#else
// T32 by default.
CheckInstructionSetT32(Assembler());
CheckInstructionSetT32(Assembler(1024));
CheckInstructionSetT32(Assembler(buffer, sizeof(buffer)));
CheckInstructionSetT32(MacroAssembler());
CheckInstructionSetT32(MacroAssembler(1024));
CheckInstructionSetT32(MacroAssembler(buffer, sizeof(buffer)));
#endif
#ifdef VIXL_INCLUDE_TARGET_A32
// Explicit A32.
CheckInstructionSetA32(Assembler(A32));
CheckInstructionSetA32(Assembler(1024, A32));
CheckInstructionSetA32(Assembler(buffer, sizeof(buffer), A32));
CheckInstructionSetA32(MacroAssembler(A32));
CheckInstructionSetA32(MacroAssembler(1024, A32));
CheckInstructionSetA32(MacroAssembler(buffer, sizeof(buffer), A32));
#endif
#ifdef VIXL_INCLUDE_TARGET_T32
// Explicit T32.
CheckInstructionSetT32(Assembler(T32));
CheckInstructionSetT32(Assembler(1024, T32));
CheckInstructionSetT32(Assembler(buffer, sizeof(buffer), T32));
CheckInstructionSetT32(MacroAssembler(T32));
CheckInstructionSetT32(MacroAssembler(1024, T32));
CheckInstructionSetT32(MacroAssembler(buffer, sizeof(buffer), T32));
#endif
}
TEST_NOASM(set_isa_empty) {
// It is possible to change the instruction set if no instructions have yet
// been generated. This test only makes sense when both A32 and T32 are
// supported.
#ifdef VIXL_INCLUDE_TARGET_AARCH32
Assembler assm;
CheckInstructionSetA32(assm);
assm.UseT32();
CheckInstructionSetT32(assm);
assm.UseA32();
CheckInstructionSetA32(assm);
assm.UseInstructionSet(T32);
CheckInstructionSetT32(assm);
assm.UseInstructionSet(A32);
CheckInstructionSetA32(assm);
MacroAssembler masm;
CheckInstructionSetA32(masm);
masm.UseT32();
CheckInstructionSetT32(masm);
masm.UseA32();
CheckInstructionSetA32(masm);
masm.UseInstructionSet(T32);
CheckInstructionSetT32(masm);
masm.UseInstructionSet(A32);
CheckInstructionSetA32(masm);
#endif
}
TEST_NOASM(set_isa_noop) {
// It is possible to call a no-op UseA32/T32 or UseInstructionSet even if
// one or more instructions have been generated.
#ifdef VIXL_INCLUDE_TARGET_A32
{
Assembler assm(A32);
CheckInstructionSetA32(assm);
CodeBufferCheckScope scope(&assm, kMaxInstructionSizeInBytes);
assm.bx(lr);
VIXL_ASSERT(assm.GetCursorOffset() > 0);
CheckInstructionSetA32(assm);
assm.UseA32();
CheckInstructionSetA32(assm);
assm.UseInstructionSet(A32);
CheckInstructionSetA32(assm);
assm.FinalizeCode();
}
{
MacroAssembler masm(A32);
CheckInstructionSetA32(masm);
masm.Bx(lr);
VIXL_ASSERT(masm.GetCursorOffset() > 0);
CheckInstructionSetA32(masm);
masm.UseA32();
CheckInstructionSetA32(masm);
masm.UseInstructionSet(A32);
CheckInstructionSetA32(masm);
masm.FinalizeCode();
}
#endif
#ifdef VIXL_INCLUDE_TARGET_T32
{
Assembler assm(T32);
CheckInstructionSetT32(assm);
CodeBufferCheckScope scope(&assm, kMaxInstructionSizeInBytes);
assm.bx(lr);
VIXL_ASSERT(assm.GetCursorOffset() > 0);
CheckInstructionSetT32(assm);
assm.UseT32();
CheckInstructionSetT32(assm);
assm.UseInstructionSet(T32);
CheckInstructionSetT32(assm);
assm.FinalizeCode();
}
{
MacroAssembler masm(T32);
CheckInstructionSetT32(masm);
masm.Bx(lr);
VIXL_ASSERT(masm.GetCursorOffset() > 0);
CheckInstructionSetT32(masm);
masm.UseT32();
CheckInstructionSetT32(masm);
masm.UseInstructionSet(T32);
CheckInstructionSetT32(masm);
masm.FinalizeCode();
}
#endif
}
TEST(logical_arithmetic_identities) {
SETUP();
START();
Label blob_1;
__ Bind(&blob_1);
__ Add(r0, r0, 0);
__ And(r0, r0, 0xffffffff);
__ Bic(r0, r0, 0);
__ Eor(r0, r0, 0);
__ Orn(r0, r0, 0xffffffff);
__ Orr(r0, r0, 0);
__ Sub(r0, r0, 0);
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&blob_1) == 0);
Label blob_2;
__ Bind(&blob_2);
__ Adds(r0, r0, 0);
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&blob_2) != 0);
Label blob_3;
__ Bind(&blob_3);
__ Ands(r0, r0, 0);
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&blob_3) != 0);
Label blob_4;
__ Bind(&blob_4);
__ Bics(r0, r0, 0);
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&blob_4) != 0);
Label blob_5;
__ Bind(&blob_5);
__ Eors(r0, r0, 0);
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&blob_5) != 0);
Label blob_6;
__ Bind(&blob_6);
__ Orns(r0, r0, 0);
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&blob_6) != 0);
Label blob_7;
__ Bind(&blob_7);
__ Orrs(r0, r0, 0);
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&blob_7) != 0);
Label blob_8;
__ Bind(&blob_8);
__ Subs(r0, r0, 0);
VIXL_ASSERT(masm.GetSizeOfCodeGeneratedSince(&blob_8) != 0);
__ Mov(r0, 0xbad);
__ And(r1, r0, 0);
__ Bic(r2, r0, 0xffffffff);
__ Eor(r3, r0, 0xffffffff);
__ Orn(r4, r0, 0);
__ Orr(r5, r0, 0xffffffff);
END();
RUN();
ASSERT_EQUAL_32(0xbad, r0);
ASSERT_EQUAL_32(0, r1);
ASSERT_EQUAL_32(0, r2);
ASSERT_EQUAL_32(~0xbad, r3);
ASSERT_EQUAL_32(0xffffffff, r4);
ASSERT_EQUAL_32(0xffffffff, r5);
}
TEST(scratch_register_checks) {
// It is unsafe for users to use registers that the MacroAssembler is also
// using as scratch registers. This test checks the MacroAssembler's checking
// mechanism itself.
SETUP();
START();
{
UseScratchRegisterScope temps(&masm);
// 'ip' is a scratch register by default.
VIXL_CHECK(masm.GetScratchRegisterList()->GetList() ==
(1u << ip.GetCode()));
VIXL_CHECK(temps.IsAvailable(ip));
// Integer registers have no complicated aliasing so
// masm.AliasesAvailableScratchRegister(reg) == temps.IsAvailable(reg).
for (unsigned i = 0; i < kNumberOfRegisters; i++) {
Register reg(i);
VIXL_CHECK(masm.AliasesAvailableScratchRegister(reg) ==
temps.IsAvailable(reg));
}
}
END();
}
TEST(scratch_register_checks_v) {
// It is unsafe for users to use registers that the MacroAssembler is also
// using as scratch registers. This test checks the MacroAssembler's checking
// mechanism itself.
SETUP();
{
UseScratchRegisterScope temps(&masm);
// There is no default floating-point scratch register. Add temps of various
// sizes to check handling of aliased registers.
VIXL_CHECK(masm.GetScratchVRegisterList()->GetList() == 0);
temps.Include(q15);
temps.Include(d15);
temps.Include(s15);
temps.Include(d4);
temps.Include(d5);
temps.Include(s24);
temps.Include(s25);
temps.Include(s26);
temps.Include(s27);
temps.Include(q0);
// See VRegisterList for details of the list encoding.
VIXL_CHECK(masm.GetScratchVRegisterList()->GetList() ==
UINT64_C(0xf0000000cf008f0f));
// | || || |
// q15 d15| || q0
// s24-s27 |d4-d5
// s15
// Simple checks: Included registers are available.
VIXL_CHECK(temps.IsAvailable(q15));
VIXL_CHECK(temps.IsAvailable(d15));
VIXL_CHECK(temps.IsAvailable(s15));
VIXL_CHECK(temps.IsAvailable(d4));
VIXL_CHECK(temps.IsAvailable(d5));
VIXL_CHECK(temps.IsAvailable(s24));
VIXL_CHECK(temps.IsAvailable(s25));
VIXL_CHECK(temps.IsAvailable(s26));
VIXL_CHECK(temps.IsAvailable(s27));
VIXL_CHECK(temps.IsAvailable(q0));
// Each available S register should mark the corresponding D and Q registers
// as aliasing an available scratch register.
for (unsigned s = 0; s < kNumberOfSRegisters; s++) {
if (temps.IsAvailable(SRegister(s))) {
VIXL_CHECK(masm.AliasesAvailableScratchRegister(SRegister(s)));
VIXL_CHECK(masm.AliasesAvailableScratchRegister(DRegister(s / 2)));
VIXL_CHECK(masm.AliasesAvailableScratchRegister(QRegister(s / 4)));
} else {
// AliasesAvailableScratchRegiters == IsAvailable for S registers.
VIXL_CHECK(!masm.AliasesAvailableScratchRegister(SRegister(s)));
}
}
// Similar checks for high D registers.
unsigned first_high_d_register = kNumberOfSRegisters / 2;
for (unsigned d = first_high_d_register; d < kMaxNumberOfDRegisters; d++) {
if (temps.IsAvailable(DRegister(d))) {
VIXL_CHECK(masm.AliasesAvailableScratchRegister(DRegister(d)));
VIXL_CHECK(masm.AliasesAvailableScratchRegister(QRegister(d / 2)));
} else {
// AliasesAvailableScratchRegiters == IsAvailable for high D registers.
VIXL_CHECK(!masm.AliasesAvailableScratchRegister(DRegister(d)));
}
}
}
}
TEST(nop) {
SETUP();
Label start;
__ Bind(&start);
__ Nop();
size_t nop_size = (isa == T32) ? k16BitT32InstructionSizeInBytes
: kA32InstructionSizeInBytes;
// `MacroAssembler::Nop` must generate at least one nop.
VIXL_CHECK(masm.GetSizeOfCodeGeneratedSince(&start) >= nop_size);
masm.FinalizeCode();
}
// Check that `GetPoolCheckpoint()` is precise.
TEST(literal_pool_margin) {
SETUP();
START();
VIXL_CHECK(test.PoolIsEmpty());
// Create a single literal.
__ Ldrd(r0, r1, 0x1234567890abcdef);
VIXL_CHECK(!test.PoolIsEmpty());
// Generate code to fill all the margin we have before generating the literal
// pool.
int32_t margin = test.GetPoolCheckpoint() - masm.GetCursorOffset();
int32_t end = test.GetPoolCheckpoint();
{
ExactAssemblyScope scope(&masm, margin, ExactAssemblyScope::kExactSize);
// Opening the scope should not have triggered the emission of the literal
// pool.
VIXL_CHECK(!test.PoolIsEmpty());
while (masm.GetCursorOffset() < end) {
__ nop();
}
VIXL_CHECK(masm.GetCursorOffset() == end);
}
// There should be no margin left to emit the literal pool.
VIXL_CHECK(!test.PoolIsEmpty());
VIXL_CHECK(test.GetPoolCheckpoint() == masm.GetCursorOffset());
// So emitting a single instruction should force emission of the pool.
__ Nop();
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
}
// Check that `GetPoolCheckpoint()` is precise.
TEST(veneer_pool_margin) {
SETUP();
START();
VIXL_CHECK(test.PoolIsEmpty());
// Create a single veneer.
Label target;
__ B(eq, &target);
VIXL_CHECK(!test.PoolIsEmpty());
// Generate code to fill all the margin we have before generating the veneer
// pool.
int32_t margin = test.GetPoolCheckpoint() - masm.GetCursorOffset();
int32_t end = test.GetPoolCheckpoint();
{
ExactAssemblyScope scope(&masm, margin, ExactAssemblyScope::kExactSize);
// Opening the scope should not have triggered the emission of the veneer
// pool.
VIXL_CHECK(!test.PoolIsEmpty());
while (masm.GetCursorOffset() < end) {
__ nop();
}
VIXL_CHECK(masm.GetCursorOffset() == end);
}
// There should be no margin left to emit the veneer pool.
VIXL_CHECK(test.GetPoolCheckpoint() == masm.GetCursorOffset());
// So emitting a single instruction should force emission of the pool.
// We cannot simply check that the veneer pool is empty, because the veneer
// emitted for the CBZ instruction above is itself tracked by the veneer
// mechanisms. Instead, check that some 'unexpected' code is generated.
Label check;
__ Bind(&check);
{
ExactAssemblyScope scope(&masm, 2, ExactAssemblyScope::kMaximumSize);
// Do not actually generate any code.
}
VIXL_CHECK(masm.GetSizeOfCodeGeneratedSince(&check) > 0);
__ Bind(&target);
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
}
TEST_T32(near_branch_fuzz) {
SETUP();
START();
uint16_t seed[3] = {1, 2, 3};
seed48(seed);
const int label_count = 31;
bool allbound;
Label* l;
// Use multiple iterations, as each produces a different predictably random
// sequence.
const int iterations = 64;
int loop_count = 0;
__ Mov(r1, 0);
// Initialise the status flags to Z set.
__ Cmp(r1, r1);
// Gradually increasing the number of cases effectively increases the
// probability of nops being emitted in the sequence. The branch-to-bind
// ratio in the sequence is fixed at 4:1 by the ratio of cases.
for (int case_count = 6; case_count < 37; case_count++) {
for (int iter = 0; iter < iterations; iter++) {
// Reset local state.
allbound = false;
l = new Label[label_count];
// Set r0 != 0 to force no branches to be taken. Also acts as a marker
// between each iteration in the disassembly.
__ Mov(r0, 1);
for (;;) {
uint32_t inst_case = static_cast<uint32_t>(mrand48()) % case_count;
uint32_t label_index = static_cast<uint32_t>(mrand48()) % label_count;
switch (inst_case) {
case 0: // Bind.
if (!l[label_index].IsBound()) {
__ Bind(&l[label_index]);
// We should hit each label exactly once (because the branches are
// never taken). Keep a counter to verify this.
loop_count++;
__ Add(r1, r1, 1);
}
break;
case 1: // Compare and branch if zero (untaken as r0 == 1).
__ Cbz(r0, &l[label_index]);
break;
case 2: { // Compare and branch if not zero.
Label past_branch;
__ B(eq, &past_branch, kNear);
__ Cbnz(r0, &l[label_index]);
__ Bind(&past_branch);
break;
}
case 3: { // Unconditional branch preferred near.
Label past_branch;
__ B(eq, &past_branch, kNear);
__ B(&l[label_index], kNear);
__ Bind(&past_branch);
break;
}
case 4: // Conditional branch (untaken as Z set) preferred near.
__ B(ne, &l[label_index], kNear);
break;
default: // Nop.
__ Nop();
break;
}
// If all labels have been bound, exit the inner loop and finalise the
// code.
allbound = true;
for (int i = 0; i < label_count; i++) {
allbound = allbound && l[i].IsBound();
}
if (allbound) break;
}
// Ensure that the veneer pools are emitted, to keep each branch/bind test
// independent. We will generate more code following this.
masm.FinalizeCode(MacroAssembler::kFallThrough);
delete[] l;
}
}
END();
RUN();
ASSERT_EQUAL_32(loop_count, r1);
}
TEST_T32(near_branch_and_literal_fuzz) {
SETUP();
START();
uint16_t seed[3] = {1, 2, 3};
seed48(seed);
const int label_count = 15;
const int literal_count = 31;
bool allbound;
Label* labels;
uint64_t* literal_values;
Literal<uint64_t>* literals[literal_count];
// Use multiple iterations, as each produces a different predictably random
// sequence.
const int iterations = 128;
const int n_cases = 20;
int loop_count = 0;
__ Mov(r1, 0);
// If the value of r4 changes then the test fails.
__ Mov(r4, 42);
// This test generates a mix of 20 different code sequences (see switch case
// below). The cases are split in 4 groups:
//
// - 0..3: Generate various amount of nops.
// - 4..7: Generate various load intstructions with literals.
// - 8..14: Generate various branch instructions.
// - 15..19: Generate various amount of nops.
//
// The idea behind this is that we can have a window of size N which we can
// slide across these cases. And as a result, randomly generate sequences with
// a different ratio of:
// - "nops vs literals"
// - "literal vs veneers"
// - "veneers vs nops"
//
// In this test, we grow a window from 5 to 14, and then slide this window
// across all cases each time. We call this sliding a "ratio", which is in
// fact an offset from the first case of the switch.
for (uint32_t window = 5; window < 14; window++) {
for (uint32_t ratio = 0; ratio < static_cast<uint32_t>(n_cases - window);
ratio++) {
for (int iter = 0; iter < iterations; iter++) {
Label fail;
Label end;
// Reset local state.
allbound = false;
labels = new Label[label_count];
// Create new literal values.
literal_values = new uint64_t[literal_count];
for (int lit = 0; lit < literal_count; lit++) {
// TODO: Generate pseudo-random data for literals. At the moment, the
// disassembler breaks if we do this.
literal_values[lit] = lit;
literals[lit] = new Literal<uint64_t>(literal_values[lit]);
}
for (;;) {
uint32_t inst_case =
(static_cast<uint32_t>(mrand48()) % window) + ratio;
uint32_t label_index = static_cast<uint32_t>(mrand48()) % label_count;
uint32_t literal_index =
static_cast<uint32_t>(mrand48()) % literal_count;
if (inst_case == ratio) {
if (!labels[label_index].IsBound()) {
__ Bind(&labels[label_index]);
// We should hit each label exactly once (because the branches are
// never taken). Keep a counter to verify this.
loop_count++;
__ Add(r1, r1, 1);
continue;
}
}
switch (inst_case) {
case 0:
__ Nop();
break;
case 1:
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
break;
case 2:
__ Nop();
__ Nop();
__ Nop();
break;
case 3:
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
break;
case 4:
__ Ldr(r2, literals[literal_index]);
__ Cmp(r2, static_cast<uint32_t>(literal_values[literal_index]));
__ B(ne, &fail);
__ Mov(r2, 0);
break;
case 5:
__ Ldrb(r2, literals[literal_index]);
__ Cmp(r2,
static_cast<uint32_t>(literal_values[literal_index]) &
0xff);
__ B(ne, &fail);
__ Mov(r2, 0);
break;
case 6:
__ Ldrd(r2, r3, literals[literal_index]);
__ Cmp(r2, static_cast<uint32_t>(literal_values[literal_index]));
__ B(ne, &fail);
__ Mov(r2, 0);
__ Cmp(r3,
static_cast<uint32_t>(literal_values[literal_index] >>
32));
__ B(ne, &fail);
__ Mov(r3, 0);
break;
case 7:
__ Vldr(s0, literals[literal_index]);
__ Vmov(s1, static_cast<uint32_t>(literal_values[literal_index]));
__ Vcmp(s0, s1);
__ B(ne, &fail);
__ Vmov(s0, 0);
break;
case 8: {
Label past_branch;
__ B(&past_branch, kNear);
__ Cbz(r0, &labels[label_index]);
__ Bind(&past_branch);
break;
}
case 9: {
Label past_branch;
__ B(&past_branch, kNear);
__ Cbnz(r0, &labels[label_index]);
__ Bind(&past_branch);
break;
}
case 10: {
Label past_branch;
__ B(&past_branch, kNear);
__ B(ne, &labels[label_index], kNear);
__ Bind(&past_branch);
break;
}
case 11: {
Label past_branch;
__ B(&past_branch, kNear);
__ B(&labels[label_index], kNear);
__ Bind(&past_branch);
break;
}
case 12: {
Label past_branch;
__ B(&past_branch, kNear);
__ B(ne, &labels[label_index]);
__ Bind(&past_branch);
break;
}
case 13: {
Label past_branch;
__ B(&past_branch, kNear);
__ B(&labels[label_index]);
__ Bind(&past_branch);
break;
}
case 14: {
Label past_branch;
__ B(&past_branch, kNear);
__ Bl(&labels[label_index]);
__ Bind(&past_branch);
break;
}
case 15:
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
break;
case 16:
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
break;
case 17:
__ Nop();
__ Nop();
__ Nop();
__ Nop();
break;
case 18:
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
__ Nop();
break;
case 19:
__ Nop();
__ Nop();
break;
default:
VIXL_UNREACHABLE();
break;
}
// If all labels have been bound, exit the inner loop and finalise the
// code.
allbound = true;
for (int i = 0; i < label_count; i++) {
allbound = allbound && labels[i].IsBound();
}
if (allbound) break;
}
__ B(&end);
__ Bind(&fail);
__ Mov(r4, 0);
__ Bind(&end);
// Ensure that the veneer pools are emitted, to keep each branch/bind
// test
// independent.
masm.FinalizeCode(MacroAssembler::kFallThrough);
delete[] labels;
for (int lit = 0; lit < literal_count; lit++) {
delete literals[lit];
}
}
}
}
END();
RUN();
ASSERT_EQUAL_32(loop_count, r1);
ASSERT_EQUAL_32(42, r4);
}
#ifdef VIXL_INCLUDE_TARGET_T32
TEST_NOASM(code_buffer_precise_growth) {
static const int kBaseBufferSize = 16;
MacroAssembler masm(kBaseBufferSize, T32);
VIXL_CHECK(masm.GetBuffer()->GetCapacity() == kBaseBufferSize);
{
// Fill the buffer with nops.
ExactAssemblyScope scope(&masm,
kBaseBufferSize,
ExactAssemblyScope::kExactSize);
for (int i = 0; i < kBaseBufferSize; i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
// The buffer should not have grown yet.
VIXL_CHECK(masm.GetBuffer()->GetCapacity() == kBaseBufferSize);
// Generating a single instruction should force the buffer to grow.
__ Nop();
VIXL_CHECK(masm.GetBuffer()->GetCapacity() > kBaseBufferSize);
masm.FinalizeCode();
}
#endif
#ifdef VIXL_INCLUDE_TARGET_T32
TEST_NOASM(out_of_space_immediately_before_EnsureEmitFor) {
static const int kBaseBufferSize = 64;
MacroAssembler masm(kBaseBufferSize, T32);
TestMacroAssembler test(&masm);
VIXL_CHECK(masm.GetBuffer()->GetCapacity() == kBaseBufferSize);
VIXL_CHECK(test.PoolIsEmpty());
// Create a veneer.
Label target;
__ Cbz(r0, &target);
VIXL_CHECK(!test.PoolIsEmpty());
VIXL_CHECK(IsUint32(masm.GetBuffer()->GetRemainingBytes()));
uint32_t space = static_cast<uint32_t>(masm.GetBuffer()->GetRemainingBytes());
{
// Fill the buffer with nops.
ExactAssemblyScope scope(&masm, space, ExactAssemblyScope::kExactSize);
for (uint32_t i = 0; i < space; i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
VIXL_CHECK(!test.PoolIsEmpty());
// The buffer should not have grown yet, and there should be no space left.
VIXL_CHECK(masm.GetBuffer()->GetCapacity() == kBaseBufferSize);
VIXL_CHECK(masm.GetBuffer()->GetRemainingBytes() == 0);
// Force emission of the veneer, at a point where there is no space available
// in the buffer.
int32_t past_cbz_range =
test.GetPoolCheckpoint() - masm.GetCursorOffset() + 1;
masm.EnsureEmitFor(past_cbz_range);
__ Bind(&target);
VIXL_CHECK(test.PoolIsEmpty());
masm.FinalizeCode();
}
#endif
TEST_NOASM(EnsureEmitFor) {
static const int kBaseBufferSize = 32;
MacroAssembler masm(kBaseBufferSize);
VIXL_CHECK(masm.GetBuffer()->GetCapacity() == kBaseBufferSize);
VIXL_CHECK(IsUint32(masm.GetBuffer()->GetRemainingBytes()));
int32_t space = static_cast<int32_t>(masm.GetBuffer()->GetRemainingBytes());
int32_t end = __ GetCursorOffset() + space;
{
// Fill the buffer with nops.
ExactAssemblyScope scope(&masm, space, ExactAssemblyScope::kExactSize);
while (__ GetCursorOffset() != end) {
__ nop();
}
}
// Test that EnsureEmitFor works.
VIXL_CHECK(!masm.GetBuffer()->HasSpaceFor(4));
masm.EnsureEmitFor(4);
VIXL_CHECK(masm.GetBuffer()->HasSpaceFor(4));
__ Nop();
masm.FinalizeCode();
}
TEST_T32(distant_literal_references) {
SETUP();
START();
Literal<uint64_t>* literal =
new Literal<uint64_t>(UINT64_C(0x0123456789abcdef),
RawLiteral::kPlacedWhenUsed,
RawLiteral::kDeletedOnPoolDestruction);
// Refer to the literal so that it is emitted early.
__ Ldr(r0, literal);
// Add enough nops to exceed the range of all loads.
int space = 5000;
{
ExactAssemblyScope scope(&masm, space, CodeBufferCheckScope::kExactSize);
VIXL_ASSERT(masm.IsUsingT32());
for (int i = 0; i < space; i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
#define ENSURE_ALIGNED() \
do { \
if (!IsMultiple<k32BitT32InstructionSizeInBytes>( \
masm.GetCursorOffset())) { \
ExactAssemblyScope scope(&masm, \
k16BitT32InstructionSizeInBytes, \
ExactAssemblyScope::kExactSize); \
__ nop(); \
} \
VIXL_ASSERT( \
IsMultiple<k32BitT32InstructionSizeInBytes>(masm.GetCursorOffset())); \
} while (0)
// The literal has already been emitted, and is out of range of all of these
// instructions. The delegates must generate fix-up code.
ENSURE_ALIGNED();
__ Ldr(r1, literal);
ENSURE_ALIGNED();
__ Ldrb(r2, literal);
ENSURE_ALIGNED();
__ Ldrsb(r3, literal);
ENSURE_ALIGNED();
__ Ldrh(r4, literal);
ENSURE_ALIGNED();
__ Ldrsh(r5, literal);
ENSURE_ALIGNED();
__ Ldrd(r6, r7, literal);
ENSURE_ALIGNED();
__ Vldr(d0, literal);
ENSURE_ALIGNED();
__ Vldr(s3, literal);
#undef ENSURE_ALIGNED
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x89abcdef, r0);
ASSERT_EQUAL_32(0x89abcdef, r1);
ASSERT_EQUAL_32(0xef, r2);
ASSERT_EQUAL_32(0xffffffef, r3);
ASSERT_EQUAL_32(0xcdef, r4);
ASSERT_EQUAL_32(0xffffcdef, r5);
ASSERT_EQUAL_32(0x89abcdef, r6);
ASSERT_EQUAL_32(0x01234567, r7);
ASSERT_EQUAL_FP64(RawbitsToDouble(0x0123456789abcdef), d0);
ASSERT_EQUAL_FP32(RawbitsToFloat(0x89abcdef), s3);
}
TEST_T32(distant_literal_references_unaligned_pc) {
SETUP();
START();
Literal<uint64_t>* literal =
new Literal<uint64_t>(UINT64_C(0x0123456789abcdef),
RawLiteral::kPlacedWhenUsed,
RawLiteral::kDeletedOnPoolDestruction);
// Refer to the literal so that it is emitted early.
__ Ldr(r0, literal);
// Add enough nops to exceed the range of all loads, leaving the PC aligned
// to only a two-byte boundary.
int space = 5002;
{
ExactAssemblyScope scope(&masm, space, CodeBufferCheckScope::kExactSize);
VIXL_ASSERT(masm.IsUsingT32());
for (int i = 0; i < space; i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
#define ENSURE_NOT_ALIGNED() \
do { \
if (IsMultiple<k32BitT32InstructionSizeInBytes>(masm.GetCursorOffset())) { \
ExactAssemblyScope scope(&masm, \
k16BitT32InstructionSizeInBytes, \
ExactAssemblyScope::kExactSize); \
__ nop(); \
} \
VIXL_ASSERT( \
!IsMultiple<k32BitT32InstructionSizeInBytes>(masm.GetCursorOffset())); \
} while (0)
// The literal has already been emitted, and is out of range of all of these
// instructions. The delegates must generate fix-up code.
ENSURE_NOT_ALIGNED();
__ Ldr(r1, literal);
ENSURE_NOT_ALIGNED();
__ Ldrb(r2, literal);
ENSURE_NOT_ALIGNED();
__ Ldrsb(r3, literal);
ENSURE_NOT_ALIGNED();
__ Ldrh(r4, literal);
ENSURE_NOT_ALIGNED();
__ Ldrsh(r5, literal);
ENSURE_NOT_ALIGNED();
__ Ldrd(r6, r7, literal);
{
// TODO: We currently require an extra scratch register for these cases
// because MemOperandComputationHelper isn't able to fit add_sub_offset into
// a single 'sub' instruction, so 'pc' gets preserved first. The same
// problem technically exists for the other loads, but vldr is particularly
// badly affected because vldr cannot set the low bits in its offset mask,
// so the add/sub operand is likely to be difficult to encode.
//
// At the moment, we get this:
// mov r8, pc
// mov ip, #5118
// sub r8, pc
// vldr d0, [r8, #48]
//
// We should be able to generate something like this:
// sub ip, pc, #0x1300 // 5118 & 0xff00
// sub ip, #0xfe // 5118 & 0x00ff
// vldr d0, [ip, #48]
UseScratchRegisterScope temps(&masm);
temps.Include(r8);
ENSURE_NOT_ALIGNED();
__ Vldr(d0, literal);
ENSURE_NOT_ALIGNED();
__ Vldr(s3, literal);
}
#undef ENSURE_NOT_ALIGNED
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x89abcdef, r0);
ASSERT_EQUAL_32(0x89abcdef, r1);
ASSERT_EQUAL_32(0xef, r2);
ASSERT_EQUAL_32(0xffffffef, r3);
ASSERT_EQUAL_32(0xcdef, r4);
ASSERT_EQUAL_32(0xffffcdef, r5);
ASSERT_EQUAL_32(0x89abcdef, r6);
ASSERT_EQUAL_32(0x01234567, r7);
ASSERT_EQUAL_FP64(RawbitsToDouble(0x0123456789abcdef), d0);
ASSERT_EQUAL_FP32(RawbitsToFloat(0x89abcdef), s3);
}
TEST_T32(distant_literal_references_short_range) {
SETUP();
START();
Literal<uint64_t>* literal =
new Literal<uint64_t>(UINT64_C(0x0123456789abcdef),
RawLiteral::kPlacedWhenUsed,
RawLiteral::kDeletedOnPoolDestruction);
// Refer to the literal so that it is emitted early.
__ Vldr(s4, literal);
// Add enough nops to exceed the range of the loads, but not the adr that will
// be generated to read the PC.
int space = 4000;
{
ExactAssemblyScope scope(&masm, space, CodeBufferCheckScope::kExactSize);
VIXL_ASSERT(masm.IsUsingT32());
for (int i = 0; i < space; i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
#define ENSURE_ALIGNED() \
do { \
if (!IsMultiple<k32BitT32InstructionSizeInBytes>( \
masm.GetCursorOffset())) { \
ExactAssemblyScope scope(&masm, \
k16BitT32InstructionSizeInBytes, \
ExactAssemblyScope::kExactSize); \
__ nop(); \
} \
VIXL_ASSERT( \
IsMultiple<k32BitT32InstructionSizeInBytes>(masm.GetCursorOffset())); \
} while (0)
// The literal has already been emitted, and is out of range of all of these
// instructions. The delegates must generate fix-up code.
ENSURE_ALIGNED();
__ Ldr(r1, literal);
ENSURE_ALIGNED();
__ Ldrb(r2, literal);
ENSURE_ALIGNED();
__ Ldrsb(r3, literal);
ENSURE_ALIGNED();
__ Ldrh(r4, literal);
ENSURE_ALIGNED();
__ Ldrsh(r5, literal);
ENSURE_ALIGNED();
__ Ldrd(r6, r7, literal);
ENSURE_ALIGNED();
__ Vldr(d0, literal);
ENSURE_ALIGNED();
__ Vldr(s3, literal);
#undef ENSURE_ALIGNED
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_FP32(RawbitsToFloat(0x89abcdef), s4);
ASSERT_EQUAL_32(0x89abcdef, r1);
ASSERT_EQUAL_32(0xef, r2);
ASSERT_EQUAL_32(0xffffffef, r3);
ASSERT_EQUAL_32(0xcdef, r4);
ASSERT_EQUAL_32(0xffffcdef, r5);
ASSERT_EQUAL_32(0x89abcdef, r6);
ASSERT_EQUAL_32(0x01234567, r7);
ASSERT_EQUAL_FP64(RawbitsToDouble(0x0123456789abcdef), d0);
ASSERT_EQUAL_FP32(RawbitsToFloat(0x89abcdef), s3);
}
TEST_T32(distant_literal_references_short_range_unaligned_pc) {
SETUP();
START();
Literal<uint64_t>* literal =
new Literal<uint64_t>(UINT64_C(0x0123456789abcdef),
RawLiteral::kPlacedWhenUsed,
RawLiteral::kDeletedOnPoolDestruction);
// Refer to the literal so that it is emitted early.
__ Vldr(s4, literal);
// Add enough nops to exceed the range of the loads, but not the adr that will
// be generated to read the PC.
int space = 4000;
{
ExactAssemblyScope scope(&masm, space, CodeBufferCheckScope::kExactSize);
VIXL_ASSERT(masm.IsUsingT32());
for (int i = 0; i < space; i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
#define ENSURE_NOT_ALIGNED() \
do { \
if (IsMultiple<k32BitT32InstructionSizeInBytes>(masm.GetCursorOffset())) { \
ExactAssemblyScope scope(&masm, \
k16BitT32InstructionSizeInBytes, \
ExactAssemblyScope::kExactSize); \
__ nop(); \
} \
VIXL_ASSERT( \
!IsMultiple<k32BitT32InstructionSizeInBytes>(masm.GetCursorOffset())); \
} while (0)
// The literal has already been emitted, and is out of range of all of these
// instructions. The delegates must generate fix-up code.
ENSURE_NOT_ALIGNED();
__ Ldr(r1, literal);
ENSURE_NOT_ALIGNED();
__ Ldrb(r2, literal);
ENSURE_NOT_ALIGNED();
__ Ldrsb(r3, literal);
ENSURE_NOT_ALIGNED();
__ Ldrh(r4, literal);
ENSURE_NOT_ALIGNED();
__ Ldrsh(r5, literal);
ENSURE_NOT_ALIGNED();
__ Ldrd(r6, r7, literal);
ENSURE_NOT_ALIGNED();
__ Vldr(d0, literal);
ENSURE_NOT_ALIGNED();
__ Vldr(s3, literal);
#undef ENSURE_NOT_ALIGNED
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_FP32(RawbitsToFloat(0x89abcdef), s4);
ASSERT_EQUAL_32(0x89abcdef, r1);
ASSERT_EQUAL_32(0xef, r2);
ASSERT_EQUAL_32(0xffffffef, r3);
ASSERT_EQUAL_32(0xcdef, r4);
ASSERT_EQUAL_32(0xffffcdef, r5);
ASSERT_EQUAL_32(0x89abcdef, r6);
ASSERT_EQUAL_32(0x01234567, r7);
ASSERT_EQUAL_FP64(RawbitsToDouble(0x0123456789abcdef), d0);
ASSERT_EQUAL_FP32(RawbitsToFloat(0x89abcdef), s3);
}
TEST_T32(distant_literal_references_long_range) {
SETUP();
START();
Literal<uint64_t>* literal =
new Literal<uint64_t>(UINT64_C(0x0123456789abcdef),
RawLiteral::kPlacedWhenUsed,
RawLiteral::kDeletedOnPoolDestruction);
// Refer to the literal so that it is emitted early.
__ Ldr(r0, literal);
#define PAD_WITH_NOPS(space) \
do { \
{ \
ExactAssemblyScope scope(&masm, \
space, \
CodeBufferCheckScope::kExactSize); \
VIXL_ASSERT(masm.IsUsingT32()); \
for (int i = 0; i < space; i += k16BitT32InstructionSizeInBytes) { \
__ nop(); \
} \
} \
} while (0)
// Add enough nops to exceed the range of all loads.
PAD_WITH_NOPS(5000);
// The literal has already been emitted, and is out of range of all of these
// instructions. The delegates must generate fix-up code.
__ Ldr(r1, literal);
__ Ldrb(r2, literal);
__ Ldrsb(r3, literal);
__ Ldrh(r4, literal);
__ Ldrsh(r5, literal);
__ Ldrd(r6, r7, literal);
__ Vldr(d0, literal);
__ Vldr(s3, literal);
// Add enough nops to exceed the range of the adr+sub sequence.
PAD_WITH_NOPS(0x421000);
__ Ldr(r1, literal);
__ Ldrb(r2, literal);
__ Ldrsb(r3, literal);
__ Ldrh(r4, literal);
__ Ldrsh(r5, literal);
__ Ldrd(r6, r7, literal);
{
// TODO: We currently require an extra scratch register for these cases. We
// should be able to optimise the code generation to avoid this requirement
// (and in many cases avoid a 32-bit instruction).
UseScratchRegisterScope temps(&masm);
temps.Include(r8);
__ Vldr(d0, literal);
__ Vldr(s3, literal);
}
#undef PAD_WITH_NOPS
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x89abcdef, r0);
ASSERT_EQUAL_32(0x89abcdef, r1);
ASSERT_EQUAL_32(0xef, r2);
ASSERT_EQUAL_32(0xffffffef, r3);
ASSERT_EQUAL_32(0xcdef, r4);
ASSERT_EQUAL_32(0xffffcdef, r5);
ASSERT_EQUAL_32(0x89abcdef, r6);
ASSERT_EQUAL_32(0x01234567, r7);
ASSERT_EQUAL_FP64(RawbitsToDouble(0x0123456789abcdef), d0);
ASSERT_EQUAL_FP32(RawbitsToFloat(0x89abcdef), s3);
}
TEST(barriers) {
// Generate all supported barriers, this is just a smoke test
SETUP();
START();
// DMB
__ Dmb(SY);
__ Dmb(ST);
__ Dmb(ISH);
__ Dmb(ISHST);
__ Dmb(NSH);
__ Dmb(NSHST);
__ Dmb(OSH);
__ Dmb(OSHST);
// DSB
__ Dsb(SY);
__ Dsb(ST);
__ Dsb(ISH);
__ Dsb(ISHST);
__ Dsb(NSH);
__ Dsb(NSHST);
__ Dsb(OSH);
__ Dsb(OSHST);
// ISB
__ Isb(SY);
END();
}
TEST(preloads) {
// Smoke test for various pld/pli forms.
SETUP();
START();
// PLD immediate
__ Pld(MemOperand(sp, 0));
__ Pld(MemOperand(r0, 0));
__ Pld(MemOperand(r1, 123));
__ Pld(MemOperand(r2, 1234));
__ Pld(MemOperand(r3, 4095));
__ Pld(MemOperand(r4, -123));
__ Pld(MemOperand(r5, -255));
if (masm.IsUsingA32()) {
__ Pld(MemOperand(r6, -1234));
__ Pld(MemOperand(r7, -4095));
}
// PLDW immediate
__ Pldw(MemOperand(sp, 0));
__ Pldw(MemOperand(r0, 0));
__ Pldw(MemOperand(r1, 123));
__ Pldw(MemOperand(r2, 1234));
__ Pldw(MemOperand(r3, 4095));
__ Pldw(MemOperand(r4, -123));
__ Pldw(MemOperand(r5, -255));
if (masm.IsUsingA32()) {
__ Pldw(MemOperand(r6, -1234));
__ Pldw(MemOperand(r7, -4095));
}
// PLD register
__ Pld(MemOperand(r0, r1));
__ Pld(MemOperand(r0, r1, LSL, 1));
__ Pld(MemOperand(r0, r1, LSL, 2));
__ Pld(MemOperand(r0, r1, LSL, 3));
if (masm.IsUsingA32()) {
__ Pld(MemOperand(r0, r1, LSL, 4));
__ Pld(MemOperand(r0, r1, LSL, 20));
}
// PLDW register
__ Pldw(MemOperand(r0, r1));
__ Pldw(MemOperand(r0, r1, LSL, 1));
__ Pldw(MemOperand(r0, r1, LSL, 2));
__ Pldw(MemOperand(r0, r1, LSL, 3));
if (masm.IsUsingA32()) {
__ Pldw(MemOperand(r0, r1, LSL, 4));
__ Pldw(MemOperand(r0, r1, LSL, 20));
}
// PLI immediate
__ Pli(MemOperand(sp, 0));
__ Pli(MemOperand(r0, 0));
__ Pli(MemOperand(r1, 123));
__ Pli(MemOperand(r2, 1234));
__ Pli(MemOperand(r3, 4095));
__ Pli(MemOperand(r4, -123));
__ Pli(MemOperand(r5, -255));
if (masm.IsUsingA32()) {
__ Pli(MemOperand(r6, -1234));
__ Pli(MemOperand(r7, -4095));
}
// PLI register
__ Pli(MemOperand(r0, r1));
__ Pli(MemOperand(r0, r1, LSL, 1));
__ Pli(MemOperand(r0, r1, LSL, 2));
__ Pli(MemOperand(r0, r1, LSL, 3));
if (masm.IsUsingA32()) {
__ Pli(MemOperand(r0, r1, LSL, 4));
__ Pli(MemOperand(r0, r1, LSL, 20));
}
END();
}
TEST_T32(veneer_mirrored_branches) {
SETUP();
START();
const int kMaxBranchCount = 256;
for (int branch_count = 1; branch_count < kMaxBranchCount; branch_count++) {
Label* targets = new Label[branch_count];
for (int i = 0; i < branch_count; i++) {
__ Cbz(r0, &targets[i]);
}
for (int i = 0; i < branch_count; i++) {
__ Bind(&targets[branch_count - i - 1]);
__ Orr(r0, r0, r0);
}
delete[] targets;
}
END();
}
TEST_T32(branch_fuzz_example) {
SETUP();
START();
Label l[64];
__ And(r0, r0, r0);
__ Cbz(r0, &l[30]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[22]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[1]);
__ Cbz(r0, &l[15]);
__ Cbz(r0, &l[9]);
__ Cbz(r0, &l[6]);
__ Bind(&l[26]);
__ Cbz(r0, &l[29]);
__ And(r0, r0, r0);
__ And(r0, r0, r0);
__ Cbz(r0, &l[22]);
__ Bind(&l[12]);
__ Bind(&l[22]);
__ Cbz(r0, &l[10]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[30]);
__ Cbz(r0, &l[17]);
__ Cbz(r0, &l[27]);
__ Cbz(r0, &l[11]);
__ Bind(&l[7]);
__ Cbz(r0, &l[18]);
__ Bind(&l[14]);
__ Cbz(r0, &l[1]);
__ Bind(&l[18]);
__ Cbz(r0, &l[11]);
__ Cbz(r0, &l[6]);
__ Bind(&l[21]);
__ Cbz(r0, &l[28]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[28]);
__ Cbz(r0, &l[22]);
__ Bind(&l[23]);
__ Cbz(r0, &l[21]);
__ Cbz(r0, &l[28]);
__ Cbz(r0, &l[9]);
__ Bind(&l[9]);
__ Cbz(r0, &l[4]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[10]);
__ And(r0, r0, r0);
__ Bind(&l[8]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[10]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[17]);
__ Bind(&l[10]);
__ Cbz(r0, &l[8]);
__ Cbz(r0, &l[25]);
__ Cbz(r0, &l[4]);
__ Bind(&l[28]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[16]);
__ Bind(&l[19]);
__ Cbz(r0, &l[14]);
__ Cbz(r0, &l[28]);
__ Cbz(r0, &l[26]);
__ Cbz(r0, &l[21]);
__ And(r0, r0, r0);
__ Bind(&l[24]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[24]);
__ Cbz(r0, &l[24]);
__ Cbz(r0, &l[19]);
__ Cbz(r0, &l[26]);
__ Cbz(r0, &l[4]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[27]);
__ Cbz(r0, &l[14]);
__ Cbz(r0, &l[5]);
__ Cbz(r0, &l[18]);
__ Cbz(r0, &l[5]);
__ Cbz(r0, &l[6]);
__ Cbz(r0, &l[28]);
__ Cbz(r0, &l[15]);
__ Cbz(r0, &l[0]);
__ Cbz(r0, &l[10]);
__ Cbz(r0, &l[16]);
__ Cbz(r0, &l[30]);
__ Cbz(r0, &l[8]);
__ Cbz(r0, &l[16]);
__ Cbz(r0, &l[22]);
__ Cbz(r0, &l[27]);
__ Cbz(r0, &l[12]);
__ Cbz(r0, &l[0]);
__ Cbz(r0, &l[23]);
__ Cbz(r0, &l[27]);
__ Cbz(r0, &l[16]);
__ Cbz(r0, &l[24]);
__ Cbz(r0, &l[17]);
__ Cbz(r0, &l[4]);
__ Cbz(r0, &l[11]);
__ Cbz(r0, &l[6]);
__ Cbz(r0, &l[23]);
__ Bind(&l[16]);
__ Cbz(r0, &l[10]);
__ Cbz(r0, &l[17]);
__ Cbz(r0, &l[12]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[11]);
__ Cbz(r0, &l[17]);
__ Cbz(r0, &l[1]);
__ Cbz(r0, &l[3]);
__ Cbz(r0, &l[18]);
__ Bind(&l[4]);
__ Cbz(r0, &l[31]);
__ Cbz(r0, &l[25]);
__ Cbz(r0, &l[22]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[19]);
__ Cbz(r0, &l[16]);
__ Cbz(r0, &l[21]);
__ Cbz(r0, &l[27]);
__ Bind(&l[1]);
__ Cbz(r0, &l[9]);
__ Cbz(r0, &l[13]);
__ Cbz(r0, &l[10]);
__ Cbz(r0, &l[6]);
__ Cbz(r0, &l[30]);
__ Cbz(r0, &l[28]);
__ Cbz(r0, &l[7]);
__ Cbz(r0, &l[17]);
__ Bind(&l[0]);
__ Cbz(r0, &l[13]);
__ Cbz(r0, &l[11]);
__ Cbz(r0, &l[19]);
__ Cbz(r0, &l[22]);
__ Cbz(r0, &l[9]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[15]);
__ Cbz(r0, &l[31]);
__ Cbz(r0, &l[2]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[6]);
__ Bind(&l[27]);
__ Bind(&l[13]);
__ Cbz(r0, &l[23]);
__ Cbz(r0, &l[7]);
__ Bind(&l[2]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[1]);
__ Bind(&l[15]);
__ Cbz(r0, &l[13]);
__ Cbz(r0, &l[17]);
__ Cbz(r0, &l[8]);
__ Cbz(r0, &l[30]);
__ Cbz(r0, &l[8]);
__ Cbz(r0, &l[27]);
__ Cbz(r0, &l[2]);
__ Cbz(r0, &l[31]);
__ Cbz(r0, &l[4]);
__ Cbz(r0, &l[11]);
__ Bind(&l[29]);
__ Cbz(r0, &l[7]);
__ Cbz(r0, &l[5]);
__ Cbz(r0, &l[11]);
__ Cbz(r0, &l[24]);
__ Cbz(r0, &l[9]);
__ Cbz(r0, &l[3]);
__ Cbz(r0, &l[3]);
__ Cbz(r0, &l[22]);
__ Cbz(r0, &l[19]);
__ Cbz(r0, &l[4]);
__ Bind(&l[6]);
__ And(r0, r0, r0);
__ And(r0, r0, r0);
__ Cbz(r0, &l[9]);
__ Cbz(r0, &l[3]);
__ Cbz(r0, &l[23]);
__ Cbz(r0, &l[12]);
__ Cbz(r0, &l[1]);
__ Cbz(r0, &l[22]);
__ Cbz(r0, &l[24]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[16]);
__ Cbz(r0, &l[19]);
__ Cbz(r0, &l[20]);
__ Cbz(r0, &l[1]);
__ Cbz(r0, &l[4]);
__ Cbz(r0, &l[1]);
__ Cbz(r0, &l[25]);
__ Cbz(r0, &l[21]);
__ Cbz(r0, &l[20]);
__ Cbz(r0, &l[29]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[10]);
__ Cbz(r0, &l[5]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[25]);
__ Cbz(r0, &l[26]);
__ Cbz(r0, &l[28]);
__ Cbz(r0, &l[19]);
__ And(r0, r0, r0);
__ Bind(&l[17]);
__ And(r0, r0, r0);
__ And(r0, r0, r0);
__ And(r0, r0, r0);
__ And(r0, r0, r0);
__ Cbz(r0, &l[6]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[5]);
__ Cbz(r0, &l[26]);
__ Cbz(r0, &l[28]);
__ Cbz(r0, &l[24]);
__ Bind(&l[20]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[10]);
__ Cbz(r0, &l[19]);
__ Cbz(r0, &l[6]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[13]);
__ Cbz(r0, &l[15]);
__ Cbz(r0, &l[22]);
__ Cbz(r0, &l[8]);
__ Cbz(r0, &l[6]);
__ Cbz(r0, &l[23]);
__ Cbz(r0, &l[6]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[13]);
__ Bind(&l[31]);
__ Cbz(r0, &l[14]);
__ Cbz(r0, &l[5]);
__ Cbz(r0, &l[1]);
__ Cbz(r0, &l[17]);
__ Cbz(r0, &l[27]);
__ Cbz(r0, &l[10]);
__ Cbz(r0, &l[30]);
__ Cbz(r0, &l[14]);
__ Cbz(r0, &l[24]);
__ Cbz(r0, &l[26]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[2]);
__ Cbz(r0, &l[21]);
__ Cbz(r0, &l[5]);
__ Cbz(r0, &l[24]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[24]);
__ Cbz(r0, &l[17]);
__ And(r0, r0, r0);
__ And(r0, r0, r0);
__ Cbz(r0, &l[24]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[17]);
__ Cbz(r0, &l[12]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[9]);
__ Cbz(r0, &l[9]);
__ Cbz(r0, &l[31]);
__ Cbz(r0, &l[25]);
__ And(r0, r0, r0);
__ And(r0, r0, r0);
__ Cbz(r0, &l[13]);
__ Cbz(r0, &l[14]);
__ Cbz(r0, &l[5]);
__ Cbz(r0, &l[5]);
__ Cbz(r0, &l[12]);
__ Cbz(r0, &l[3]);
__ Cbz(r0, &l[25]);
__ Bind(&l[11]);
__ Cbz(r0, &l[15]);
__ Cbz(r0, &l[20]);
__ Cbz(r0, &l[22]);
__ Cbz(r0, &l[19]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[19]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[21]);
__ Cbz(r0, &l[0]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[16]);
__ Cbz(r0, &l[28]);
__ Cbz(r0, &l[18]);
__ Cbz(r0, &l[3]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[15]);
__ Cbz(r0, &l[8]);
__ Cbz(r0, &l[25]);
__ Cbz(r0, &l[1]);
__ Cbz(r0, &l[21]);
__ Cbz(r0, &l[1]);
__ Cbz(r0, &l[29]);
__ Cbz(r0, &l[15]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[24]);
__ Cbz(r0, &l[3]);
__ Cbz(r0, &l[9]);
__ Cbz(r0, &l[9]);
__ Cbz(r0, &l[24]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[19]);
__ And(r0, r0, r0);
__ Cbz(r0, &l[30]);
__ Bind(&l[25]);
__ Bind(&l[3]);
__ Bind(&l[30]);
__ Bind(&l[5]);
END();
}
// Generate a "B" and a "Cbz" which have the same checkpoint. Without proper
// management (i.e. if the veneers were only generated at the shared
// checkpoint), one one of the branches would be out of range.
TEST_T32(veneer_simultaneous) {
SETUP();
START();
// `2046` max range - the size of the B.EQ itself.
static const int kMaxBCondRange = 1048574;
Label target_1;
Label target_2;
__ B(eq, &target_1);
int target_1_size_1 =
kMaxBCondRange - kCbzCbnzRange - k32BitT32InstructionSizeInBytes;
int end_1 = masm.GetCursorOffset() + target_1_size_1;
while (masm.GetCursorOffset() < end_1) {
__ Nop();
}
__ Cbz(r0, &target_2);
int target_1_size_2 = kCbzCbnzRange - k16BitT32InstructionSizeInBytes;
int end_2 = masm.GetCursorOffset() + target_1_size_2;
while (masm.GetCursorOffset() < end_2) {
__ Nop();
}
__ Nop();
__ Bind(&target_1);
__ Bind(&target_2);
END();
}
// Generate a "B" and a "Cbz" which have the same checkpoint and the same label.
TEST_T32(veneer_simultaneous_one_label) {
SETUP();
START();
// `2046` max range - the size of the B.EQ itself.
static const int kMaxBCondRange = 1048574;
Label target;
__ B(eq, &target);
int target_1_size_1 =
kMaxBCondRange - kCbzCbnzRange - k32BitT32InstructionSizeInBytes;
int end_1 = masm.GetCursorOffset() + target_1_size_1;
while (masm.GetCursorOffset() < end_1) {
__ Nop();
}
__ Cbz(r0, &target);
int target_1_size_2 = kCbzCbnzRange - k16BitT32InstructionSizeInBytes;
int end_2 = masm.GetCursorOffset() + target_1_size_2;
while (masm.GetCursorOffset() < end_2) {
__ Nop();
}
__ Nop();
__ Bind(&target);
END();
}
// NOTE: This test has needed modifications for the new pool manager, as it
// was testing a corner case of the previous pool managers. We keep it as
// another testcase.
TEST_T32(veneer_and_literal) {
SETUP();
START();
VIXL_CHECK(test.PoolIsEmpty());
const uint32_t ldrd_range = 1020;
const uint32_t cbz_range = 126;
const uint32_t kLabelsCount = 20;
Label labels[kLabelsCount];
// Create one literal pool entry.
__ Ldrd(r0, r1, 0x1234567890abcdef);
// Generate some nops.
uint32_t i = 0;
for (; i < ldrd_range - cbz_range - 40;
i += k16BitT32InstructionSizeInBytes) {
__ Nop();
}
// At this point, it remains cbz_range + 40 => 166 bytes before ldrd becomes
// out of range.
// We generate kLabelsCount * 4 => 80 bytes. We shouldn't generate the
// literal pool.
for (uint32_t j = 0; j < kLabelsCount; j++) {
__ Cbz(r0, &labels[j]);
__ Nop();
i += 2 * k16BitT32InstructionSizeInBytes;
}
// We generate a few more instructions.
for (; i < ldrd_range - 4 * kA32InstructionSizeInBytes;
i += k16BitT32InstructionSizeInBytes) {
__ Nop();
}
// Bind all the used labels.
for (uint32_t j = 0; j < kLabelsCount; j++) {
__ Bind(&labels[j]);
__ Nop();
}
// Now that all the labels have been bound, we have no more veneers.
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
}
// NOTE: This test has needed modifications for the new pool manager, as it
// was testing a corner case of the previous pool managers. We keep it as
// another testcase.
TEST_T32(veneer_and_literal2) {
SETUP();
START();
VIXL_CHECK(test.PoolIsEmpty());
const uint32_t ldrd_range = 1020;
const uint32_t cbz_range = 126;
const uint32_t kLabelsCount = 20;
const int32_t kTypicalMacroInstructionMaxSize =
8 * kMaxInstructionSizeInBytes;
Label labels[kLabelsCount];
// Create one literal pool entry.
__ Ldrd(r0, r1, 0x1234567890abcdef);
for (uint32_t i = 0; i < ldrd_range - cbz_range - 4 * kLabelsCount;
i += k16BitT32InstructionSizeInBytes) {
__ Nop();
}
// Add entries to the veneer pool.
for (uint32_t i = 0; i < kLabelsCount; i++) {
__ Cbz(r0, &labels[i]);
__ Nop();
}
// Generate nops up to the literal pool limit.
while (test.GetPoolCheckpoint() - masm.GetCursorOffset() >=
kTypicalMacroInstructionMaxSize) {
__ Nop();
}
// At this point, no literals and no veneers have been generated.
VIXL_ASSERT(!test.PoolIsEmpty());
// The literal pool needs to be generated.
VIXL_ASSERT(test.GetPoolCheckpoint() - masm.GetCursorOffset() <
kTypicalMacroInstructionMaxSize);
// This extra Nop will generate the pools.
__ Nop();
// Bind all the used labels.
for (uint32_t j = 0; j < kLabelsCount; j++) {
__ Bind(&labels[j]);
__ Nop();
}
// Now that all the labels have been bound, we have no more veneers.
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
}
// Use a literal when we already have a veneer pool potential size greater than
// the literal range => generate the literal immediately (not optimum but it
// works).
TEST_T32(veneer_and_literal3) {
SETUP();
START();
static const int kLabelsCount = 1000;
Label labels[kLabelsCount];
// Set the Z flag so that the following branches are not taken.
__ Movs(r0, 0);
for (int i = 0; i < kLabelsCount; i++) {
__ B(ne, &labels[i]);
}
// Create one literal pool entry.
__ Ldrd(r0, r1, 0x1234567890abcdef);
for (int i = 0; i < 10; i++) {
__ Nop();
}
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&labels[i]);
}
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
}
// Literal has to be generated sooner than veneers. However, as the literal
// pool generation would make the veneers out of range, generate the veneers
// first.
TEST_T32(veneer_and_literal4) {
SETUP();
START();
Label end;
// Set the Z flag so that the following branch is not taken.
__ Movs(r0, 0);
__ B(ne, &end);
uint32_t value = 0x1234567;
Literal<uint32_t>* literal =
new Literal<uint32_t>(value,
RawLiteral::kPlacedWhenUsed,
RawLiteral::kDeletedOnPoolDestruction);
__ Ldr(r11, literal);
// The range for ldr is 4095, the range for cbz is 127. Generate nops
// to have the ldr becomming out of range just before the cbz.
const int NUM_NOPS = 2044;
const int NUM_RANGE = 58;
const int NUM1 = NUM_NOPS - NUM_RANGE;
const int NUM2 = NUM_RANGE;
{
ExactAssemblyScope aas(&masm, 2 * NUM1, CodeBufferCheckScope::kMaximumSize);
for (int i = 0; i < NUM1; i++) {
__ nop();
}
}
__ Cbz(r1, &end);
{
ExactAssemblyScope aas(&masm, 2 * NUM2, CodeBufferCheckScope::kMaximumSize);
for (int i = 0; i < NUM2; i++) {
__ nop();
}
}
{
ExactAssemblyScope aas(&masm, 4, CodeBufferCheckScope::kMaximumSize);
__ add(r1, r1, 3);
}
__ Bind(&end);
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x1234567, r11);
}
// Literal has to be generated sooner than veneers. However, as the literal
// pool generation would make the veneers out of range, generate the veneers
// first.
TEST_T32(veneer_and_literal5) {
SETUP();
START();
static const int kTestCount = 100;
Label labels[kTestCount];
int first_test = 2000;
// Test on both sizes of the Adr range which is 4095.
for (int test = 0; test < kTestCount; test++) {
const int string_size = 1000; // A lot more than the cbz range.
std::string test_string(string_size, 'x');
StringLiteral big_literal(test_string.c_str());
__ Adr(r11, &big_literal);
{
int num_nops = first_test + test;
ExactAssemblyScope aas(&masm,
2 * num_nops,
CodeBufferCheckScope::kMaximumSize);
for (int i = 0; i < num_nops; i++) {
__ nop();
}
}
__ Cbz(r1, &labels[test]);
{
ExactAssemblyScope aas(&masm, 4, CodeBufferCheckScope::kMaximumSize);
__ add(r1, r1, 3);
}
__ Bind(&labels[test]);
// Emit the literal pool if it has not beeen emitted (it's the case for
// the lower values of test).
__ EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
}
END();
}
// Check that veneer and literals are well generated when they are out of
// range at the same time.
TEST_T32(veneer_and_literal6) {
SETUP();
START();
Label t1, t2, t3, t4, t5;
static const int kLdrdRange = 1020;
static const int kSizeForCbz = k16BitT32InstructionSizeInBytes;
__ Ldrd(r0, r1, 0x1111111111111111);
__ Ldrd(r2, r3, 0x2222222222222222);
__ Ldrd(r4, r5, 0x3333333333333333);
__ Ldrd(r6, r7, 0x4444444444444444);
__ Ldrd(r8, r9, 0x5555555555555555);
__ Ldrd(r10, r11, 0x6666666666666666);
__ Ldrd(r10, r11, 0x1234567890abcdef);
// Ldrd has a bigger range that cbz. Generate some nops before the cbzs in
// order to reach the maximum range of ldrd and cbz at the same time.
{
int nop_size = kLdrdRange - kCbzCbnzRange - 5 * kSizeForCbz;
ExactAssemblyScope scope(&masm, nop_size, CodeBufferCheckScope::kExactSize);
for (int i = 0; i < nop_size; i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
__ Cbz(r2, &t1);
__ Cbz(r2, &t2);
__ Cbz(r2, &t3);
__ Cbz(r2, &t4);
__ Cbz(r2, &t5);
// At this point, the ldrds are not out of range. It remains a kCbzCbnzRange
// margin (minus the size of the veneers).
// At this point, the literal and the veneer pools are not emitted.
const int kLdrdLiteralSize = 8;
const int kVeneerSize = 4;
CHECK_POOL_SIZE(7 * kLdrdLiteralSize + 5 * kVeneerSize);
VIXL_CHECK(test.GetPoolCheckpoint() - masm.GetCursorOffset() < kCbzCbnzRange);
// This scope will generate both veneers (they are both out of range).
{
int nop_size = kCbzCbnzRange;
ExactAssemblyScope scope(&masm, nop_size, CodeBufferCheckScope::kExactSize);
for (int i = 0; i < nop_size; i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
// Check that both literals and veneers have been emitted.
CHECK_POOL_SIZE(5 * kVeneerSize);
VIXL_CHECK(test.GetPoolCheckpoint() - masm.GetCursorOffset() > kCbzCbnzRange);
__ Bind(&t1);
__ Bind(&t2);
__ Bind(&t3);
__ Bind(&t4);
__ Bind(&t5);
CHECK_POOL_SIZE(0);
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x11111111, r0);
ASSERT_EQUAL_32(0x11111111, r1);
ASSERT_EQUAL_32(0x22222222, r2);
ASSERT_EQUAL_32(0x22222222, r3);
ASSERT_EQUAL_32(0x33333333, r4);
ASSERT_EQUAL_32(0x33333333, r5);
ASSERT_EQUAL_32(0x44444444, r6);
ASSERT_EQUAL_32(0x44444444, r7);
ASSERT_EQUAL_32(0x55555555, r8);
ASSERT_EQUAL_32(0x55555555, r9);
ASSERT_EQUAL_32(0x90abcdef, r10);
ASSERT_EQUAL_32(0x12345678, r11);
}
// Check that a label which is just bound during the MacroEmissionCheckScope
// can be used.
TEST(ldr_label_bound_during_scope) {
SETUP();
START();
Literal<uint64_t>* literal =
new Literal<uint64_t>(UINT64_C(0x1234567890abcdef),
RawLiteral::kPlacedWhenUsed,
RawLiteral::kDeletedOnPoolDestruction);
__ Ldrd(r0, r1, literal);
const int nop_size = masm.IsUsingA32() ? 4 : 2;
while (test.GetPoolCheckpoint() >=
(masm.GetCursorOffset() +
static_cast<int32_t>(kMaxInstructionSizeInBytes))) {
ExactAssemblyScope scope(&masm, nop_size, ExactAssemblyScope::kExactSize);
__ nop();
}
VIXL_ASSERT(!test.PoolIsEmpty());
// This Ldrd will first generate the pool and then use literal which has just
// been bound.
__ Ldrd(r2, r3, literal);
VIXL_ASSERT(test.PoolIsEmpty());
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0x90abcdef, r2);
ASSERT_EQUAL_32(0x12345678, r3);
}
TEST_T32(test_it_scope_and_literal_pool) {
// This test stresses the ITScope to make sure the number of bytes it tries
// to emit is in sync with the MacroEmissionCheckScope that is around it.
SETUP();
START();
// Make sure the pool is empty.
masm.EmitLiteralPool(PoolManager<int32_t>::kBranchRequired);
VIXL_CHECK(test.PoolIsEmpty());
Literal<uint64_t> l0(0xcafebeefdeadbaba);
__ Ldrd(r0, r1, &l0);
// Leave exactly as many bytes between cursor and pool emission checkpoint as
// the typical macro instruction needs (and MacroEmissionCheckScope allows
// for).
const int32_t kTypicalMacroInstructionMaxSize =
8 * kMaxInstructionSizeInBytes;
int32_t margin = test.GetPoolCheckpoint() - masm.GetCursorOffset() -
kTypicalMacroInstructionMaxSize;
int32_t end = masm.GetCursorOffset() + margin;
{
ExactAssemblyScope scope(&masm, margin, ExactAssemblyScope::kExactSize);
while (masm.GetCursorOffset() < end) {
__ nop();
}
}
VIXL_CHECK((test.GetPoolCheckpoint() - masm.GetCursorOffset()) ==
kTypicalMacroInstructionMaxSize);
// We cannot use an IT block for this instruction, hence ITScope will
// generate a branch over it.
__ Add(ne, r8, r9, 256);
END();
RUN();
// Check that the literals loaded correctly.
ASSERT_EQUAL_32(0xdeadbaba, r0);
ASSERT_EQUAL_32(0xcafebeef, r1);
}
// TODO: Remove this limitation by having a sandboxing mechanism.
#if defined(VIXL_HOST_POINTER_32)
TEST(ldm_stm_no_writeback) {
SETUP();
START();
const uint32_t src[4] = {0x12345678, 0x09abcdef, 0xc001c0de, 0xdeadbeef};
uint32_t dst1[4] = {0x00000000, 0x00000000, 0x00000000, 0x00000000};
uint32_t dst2[4] = {0x00000000, 0x00000000, 0x00000000, 0x00000000};
__ Mov(r0, reinterpret_cast<uintptr_t>(src));
__ Ldm(r0, NO_WRITE_BACK, RegisterList(r1, r2, r3, r4));
__ Ldm(r0, NO_WRITE_BACK, RegisterList(r5, r6, r9, r11));
__ Mov(r0, reinterpret_cast<uintptr_t>(dst1));
__ Stm(r0, NO_WRITE_BACK, RegisterList(r1, r2, r3, r4));
__ Mov(r0, reinterpret_cast<uintptr_t>(dst2));
__ Stm(r0, NO_WRITE_BACK, RegisterList(r5, r6, r9, r11));
END();
RUN();
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0x09abcdef, r2);
ASSERT_EQUAL_32(0xc001c0de, r3);
ASSERT_EQUAL_32(0xdeadbeef, r4);
ASSERT_EQUAL_32(0x12345678, r5);
ASSERT_EQUAL_32(0x09abcdef, r6);
ASSERT_EQUAL_32(0xc001c0de, r9);
ASSERT_EQUAL_32(0xdeadbeef, r11);
ASSERT_EQUAL_32(0x12345678, dst1[0]);
ASSERT_EQUAL_32(0x09abcdef, dst1[1]);
ASSERT_EQUAL_32(0xc001c0de, dst1[2]);
ASSERT_EQUAL_32(0xdeadbeef, dst1[3]);
ASSERT_EQUAL_32(0x12345678, dst2[0]);
ASSERT_EQUAL_32(0x09abcdef, dst2[1]);
ASSERT_EQUAL_32(0xc001c0de, dst2[2]);
ASSERT_EQUAL_32(0xdeadbeef, dst2[3]);
}
TEST(ldm_stm_writeback) {
SETUP();
START();
const uint32_t src[4] = {0x12345678, 0x09abcdef, 0xc001c0de, 0xdeadbeef};
uint32_t dst[8] = {0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000,
0x00000000};
__ Mov(r0, reinterpret_cast<uintptr_t>(src));
__ Ldm(r0, WRITE_BACK, RegisterList(r2, r3));
__ Ldm(r0, WRITE_BACK, RegisterList(r4, r5));
__ Mov(r1, reinterpret_cast<uintptr_t>(dst));
__ Stm(r1, WRITE_BACK, RegisterList(r2, r3, r4, r5));
__ Stm(r1, WRITE_BACK, RegisterList(r2, r3, r4, r5));
END();
RUN();
ASSERT_EQUAL_32(reinterpret_cast<uintptr_t>(src + 4), r0);
ASSERT_EQUAL_32(reinterpret_cast<uintptr_t>(dst + 8), r1);
ASSERT_EQUAL_32(0x12345678, r2);
ASSERT_EQUAL_32(0x09abcdef, r3);
ASSERT_EQUAL_32(0xc001c0de, r4);
ASSERT_EQUAL_32(0xdeadbeef, r5);
ASSERT_EQUAL_32(0x12345678, dst[0]);
ASSERT_EQUAL_32(0x09abcdef, dst[1]);
ASSERT_EQUAL_32(0xc001c0de, dst[2]);
ASSERT_EQUAL_32(0xdeadbeef, dst[3]);
ASSERT_EQUAL_32(0x12345678, dst[4]);
ASSERT_EQUAL_32(0x09abcdef, dst[5]);
ASSERT_EQUAL_32(0xc001c0de, dst[6]);
ASSERT_EQUAL_32(0xdeadbeef, dst[7]);
}
TEST_A32(ldm_stm_da_ib) {
SETUP();
START();
const uint32_t src1[4] = {0x33333333, 0x44444444, 0x11111111, 0x22222222};
const uint32_t src2[4] = {0x11111111, 0x22222222, 0x33333333, 0x44444444};
uint32_t dst1[4] = {0x00000000, 0x00000000, 0x00000000, 0x00000000};
uint32_t dst2[4] = {0x00000000, 0x00000000, 0x00000000, 0x00000000};
__ Mov(r11, reinterpret_cast<uintptr_t>(src1 + 3));
__ Ldmda(r11, WRITE_BACK, RegisterList(r0, r1));
__ Ldmda(r11, NO_WRITE_BACK, RegisterList(r2, r3));
__ Mov(r10, reinterpret_cast<uintptr_t>(src2) - sizeof(src2[0]));
__ Ldmib(r10, WRITE_BACK, RegisterList(r4, r5));
__ Ldmib(r10, NO_WRITE_BACK, RegisterList(r6, r7));
__ Mov(r9, reinterpret_cast<uintptr_t>(dst1 + 3));
__ Stmda(r9, WRITE_BACK, RegisterList(r0, r1));
__ Stmda(r9, NO_WRITE_BACK, RegisterList(r2, r3));
__ Mov(r8, reinterpret_cast<uintptr_t>(dst2) - sizeof(dst2[0]));
__ Stmib(r8, WRITE_BACK, RegisterList(r4, r5));
__ Stmib(r8, NO_WRITE_BACK, RegisterList(r6, r7));
END();
RUN();
ASSERT_EQUAL_32(reinterpret_cast<uintptr_t>(src1 + 1), r11);
ASSERT_EQUAL_32(reinterpret_cast<uintptr_t>(src2 + 1), r10);
ASSERT_EQUAL_32(reinterpret_cast<uintptr_t>(dst1 + 1), r9);
ASSERT_EQUAL_32(reinterpret_cast<uintptr_t>(dst2 + 1), r8);
ASSERT_EQUAL_32(0x11111111, r0);
ASSERT_EQUAL_32(0x22222222, r1);
ASSERT_EQUAL_32(0x33333333, r2);
ASSERT_EQUAL_32(0x44444444, r3);
ASSERT_EQUAL_32(0x11111111, r4);
ASSERT_EQUAL_32(0x22222222, r5);
ASSERT_EQUAL_32(0x33333333, r6);
ASSERT_EQUAL_32(0x44444444, r7);
ASSERT_EQUAL_32(0x33333333, dst1[0]);
ASSERT_EQUAL_32(0x44444444, dst1[1]);
ASSERT_EQUAL_32(0x11111111, dst1[2]);
ASSERT_EQUAL_32(0x22222222, dst1[3]);
ASSERT_EQUAL_32(0x11111111, dst2[0]);
ASSERT_EQUAL_32(0x22222222, dst2[1]);
ASSERT_EQUAL_32(0x33333333, dst2[2]);
ASSERT_EQUAL_32(0x44444444, dst2[3]);
}
TEST(ldmdb_stmdb) {
SETUP();
START();
const uint32_t src[6] =
{0x55555555, 0x66666666, 0x33333333, 0x44444444, 0x11111111, 0x22222222};
uint32_t dst[6] =
{0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000};
__ Mov(r11, reinterpret_cast<uintptr_t>(src + 6));
__ Ldmdb(r11, WRITE_BACK, RegisterList(r1, r2));
__ Ldmdb(r11, WRITE_BACK, RegisterList(r3, r4));
__ Ldmdb(r11, NO_WRITE_BACK, RegisterList(r5, r6));
__ Mov(r10, reinterpret_cast<uintptr_t>(dst + 6));
__ Stmdb(r10, WRITE_BACK, RegisterList(r5, r6));
__ Stmdb(r10, WRITE_BACK, RegisterList(r3, r4));
__ Stmdb(r10, NO_WRITE_BACK, RegisterList(r1, r2));
END();
RUN();
ASSERT_EQUAL_32(reinterpret_cast<uintptr_t>(src + 2), r11);
ASSERT_EQUAL_32(reinterpret_cast<uintptr_t>(dst + 2), r10);
ASSERT_EQUAL_32(0x11111111, r1);
ASSERT_EQUAL_32(0x22222222, r2);
ASSERT_EQUAL_32(0x33333333, r3);
ASSERT_EQUAL_32(0x44444444, r4);
ASSERT_EQUAL_32(0x55555555, r5);
ASSERT_EQUAL_32(0x66666666, r6);
ASSERT_EQUAL_32(0x11111111, dst[0]);
ASSERT_EQUAL_32(0x22222222, dst[1]);
ASSERT_EQUAL_32(0x33333333, dst[2]);
ASSERT_EQUAL_32(0x44444444, dst[3]);
ASSERT_EQUAL_32(0x55555555, dst[4]);
ASSERT_EQUAL_32(0x66666666, dst[5]);
}
#endif
TEST(blx) {
SETUP();
START();
// TODO(all): Ideally this test should jump back and forth between ARM and
// Thumb mode and should also cover BLX immediate. Update this test if we
// allow VIXL assembler to change ISA anywhere in the code buffer.
Label test_start;
Label func1;
Label func2;
__ B(&test_start);
__ Bind(&func1);
__ Mov(r0, 0x11111111);
__ Push(lr);
{
size_t size_of_generated_code;
if (masm.IsUsingA32()) {
size_of_generated_code = 7 * kA32InstructionSizeInBytes;
} else {
size_of_generated_code = 5 * k32BitT32InstructionSizeInBytes +
3 * k16BitT32InstructionSizeInBytes;
}
ExactAssemblyScope scope(&masm,
size_of_generated_code,
ExactAssemblyScope::kExactSize);
__ adr(r11, &func2);
if (masm.IsUsingT32()) {
// The jump target needs to have its least significant bit set to indicate
// that we are jumping into thumb mode.
__ orr(r11, r11, 1);
}
__ blx(r11);
__ pop(lr);
__ bx(lr);
__ bind(&func2);
__ movw(r1, 0x2222);
__ movt(r1, 0x2222);
__ bx(lr);
}
__ Bind(&test_start);
__ Mov(r0, 0xdeadc0de);
__ Mov(r1, 0xdeadc0de);
__ Bl(&func1);
END();
RUN();
// Really basic test to check that we reached the different parts of the test.
ASSERT_EQUAL_32(0x11111111, r0);
ASSERT_EQUAL_32(0x22222222, r1);
}
// Check that B with a near hint use a narrow branch when it can.
TEST_T32(b_near_hint) {
SETUP();
START();
Label start;
Label end;
__ Bind(&start);
__ Nop();
{
// Generate a branch which should be narrow.
EmissionCheckScope scope(&masm,
k16BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(&start, kNear);
}
{
ExactAssemblyScope scope(&masm,
kBNarrowRange,
ExactAssemblyScope::kExactSize);
for (int32_t i = 0; i < kBNarrowRange;
i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
{
// Generate a branch which should be wide.
EmissionCheckScope scope(&masm,
k32BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(&start, kNear);
}
{
// Generate a forward branch which should be narrow.
EmissionCheckScope scope(&masm,
k16BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(&end, kNear);
}
int32_t margin = test.GetPoolCheckpoint() - masm.GetCursorOffset();
VIXL_CHECK(margin < kBNarrowRange);
{
ExactAssemblyScope scope(&masm,
kBNarrowRange,
ExactAssemblyScope::kExactSize);
for (int32_t i = 0; i < kBNarrowRange;
i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
// A veneer should have been generated.
margin = test.GetPoolCheckpoint() - masm.GetCursorOffset();
VIXL_CHECK(margin > kBNarrowRange);
__ Bind(&end);
END();
DISASSEMBLE();
}
// Check that B with a far hint use a narrow branch only for a near backward
// branch.
TEST_T32(b_far_hint) {
SETUP();
START();
Label start;
Label end;
__ Bind(&start);
__ Nop();
{
// Generate a branch which should be narrow.
EmissionCheckScope scope(&masm,
k16BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(&start, kFar);
}
{
ExactAssemblyScope scope(&masm,
kBNarrowRange,
ExactAssemblyScope::kExactSize);
for (int32_t i = 0; i < kBNarrowRange;
i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
{
// Generate a branch which should be wide.
EmissionCheckScope scope(&masm,
k32BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(&start, kFar);
}
{
// Generate a forward branch which should be wide.
EmissionCheckScope scope(&masm,
k32BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(&end, kFar);
}
__ Bind(&end);
END();
DISASSEMBLE();
}
// Check that conditional B with a near hint use a narrow branch when it can.
TEST_T32(b_conditional_near_hint) {
SETUP();
START();
Label start;
Label end;
__ Bind(&start);
__ Nop();
{
// Generate a branch which should be narrow.
EmissionCheckScope scope(&masm,
k16BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(eq, &start, kNear);
}
{
ExactAssemblyScope scope(&masm,
kBConditionalNarrowRange,
ExactAssemblyScope::kExactSize);
for (int32_t i = 0; i < kBConditionalNarrowRange;
i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
{
// Generate a branch which should be wide.
EmissionCheckScope scope(&masm,
k32BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(eq, &start, kNear);
}
{
// Generate a forward branch which should be narrow.
EmissionCheckScope scope(&masm,
k16BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(eq, &end, kNear);
}
int32_t margin = test.GetPoolCheckpoint() - masm.GetCursorOffset();
VIXL_CHECK(margin < kBConditionalNarrowRange);
{
ExactAssemblyScope scope(&masm,
kBConditionalNarrowRange,
ExactAssemblyScope::kExactSize);
for (int32_t i = 0; i < kBConditionalNarrowRange;
i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
// A veneer should have been generated.
margin = test.GetPoolCheckpoint() - masm.GetCursorOffset();
VIXL_CHECK(margin > kBConditionalNarrowRange);
__ Bind(&end);
END();
DISASSEMBLE();
}
// Check that conditional B with a far hint use a narrow branch only for a
// near backward branch.
TEST_T32(b_conditional_far_hint) {
SETUP();
START();
Label start;
Label end;
__ Bind(&start);
__ Nop();
{
// Generate a branch which should be narrow.
EmissionCheckScope scope(&masm,
k16BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(eq, &start, kFar);
}
{
ExactAssemblyScope scope(&masm,
kBConditionalNarrowRange,
ExactAssemblyScope::kExactSize);
for (int32_t i = 0; i < kBConditionalNarrowRange;
i += k16BitT32InstructionSizeInBytes) {
__ nop();
}
}
{
// Generate a branch which should be wide.
EmissionCheckScope scope(&masm,
k32BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(eq, &start, kFar);
}
{
// Generate a forward branch which should be wide.
EmissionCheckScope scope(&masm,
k32BitT32InstructionSizeInBytes,
EmissionCheckScope::kExactSize);
__ B(eq, &end, kFar);
}
__ Bind(&end);
END();
DISASSEMBLE();
}
// Check that the veneer pool is correctly emitted even if we do a lot of narrow
// branches.
TEST_T32(b_narrow_many) {
SETUP();
START();
static const int kLabelsCount = kBNarrowRange / 2;
Label labels[kLabelsCount];
__ Mov(r0, 0);
for (int i = 0; i < kLabelsCount; i++) {
__ B(&labels[i], kNear);
}
__ Mov(r0, 1);
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&labels[i]);
}
__ Nop();
END();
RUN();
ASSERT_EQUAL_32(0, r0);
}
// Check that the veneer pool is correctly emitted even if we do a lot of narrow
// branches and cbz.
TEST_T32(b_narrow_and_cbz) {
SETUP();
START();
static const int kLabelsCount = kBNarrowRange / 4;
Label b_labels[kLabelsCount];
Label cbz_labels[kLabelsCount];
__ Mov(r0, 0);
for (int i = 0; i < kLabelsCount; i++) {
__ B(&b_labels[i], kNear);
__ Cbz(r0, &cbz_labels[i]);
}
__ Mov(r0, 1);
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&b_labels[i]);
}
__ Mov(r0, 2);
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&cbz_labels[i]);
}
__ Nop();
END();
RUN();
ASSERT_EQUAL_32(2, r0);
}
#define CHECK_SIZE_MATCH(ASM1, ASM2) \
{ \
MacroAssembler masm1(BUF_SIZE); \
masm1.UseInstructionSet(isa); \
VIXL_ASSERT(masm1.GetCursorOffset() == 0); \
masm1.ASM1; \
masm1.FinalizeCode(); \
int size1 = masm1.GetCursorOffset(); \
\
MacroAssembler masm2(BUF_SIZE); \
masm2.UseInstructionSet(isa); \
VIXL_ASSERT(masm2.GetCursorOffset() == 0); \
masm2.ASM2; \
masm2.FinalizeCode(); \
int size2 = masm2.GetCursorOffset(); \
\
bool disassemble = Test::disassemble(); \
if (size1 != size2) { \
printf("Sizes did not match:\n"); \
disassemble = true; \
} \
if (disassemble) { \
PrintDisassembler dis(std::cout, 0); \
printf("// " #ASM1 "\n"); \
if (masm1.IsUsingT32()) { \
dis.DisassembleT32Buffer(masm1.GetBuffer() \
->GetStartAddress<uint16_t*>(), \
size1); \
} else { \
dis.DisassembleA32Buffer(masm1.GetBuffer() \
->GetStartAddress<uint32_t*>(), \
size1); \
} \
printf("\n"); \
\
dis.SetCodeAddress(0); \
printf("// " #ASM2 "\n"); \
if (masm2.IsUsingT32()) { \
dis.DisassembleT32Buffer(masm2.GetBuffer() \
->GetStartAddress<uint16_t*>(), \
size2); \
} else { \
dis.DisassembleA32Buffer(masm2.GetBuffer() \
->GetStartAddress<uint32_t*>(), \
size2); \
} \
printf("\n"); \
} \
VIXL_CHECK(size1 == size2); \
}
TEST_T32(macro_assembler_commute) {
// Test that the MacroAssembler will commute operands if it means it can use a
// 16-bit instruction with the same effect.
// TODO: The commented-out tests should pass, but don't. When they are fixed,
// we should update this test.
// CHECK_SIZE_MATCH(Adc(DontCare, r7, r6, r7),
// Adc(DontCare, r7, r7, r6));
// CHECK_SIZE_MATCH(Adc(DontCare, eq, r7, r6, r7),
// Adc(DontCare, eq, r7, r7, r6));
CHECK_SIZE_MATCH(Add(DontCare, r1, r2, r7), Add(DontCare, r1, r7, r2));
CHECK_SIZE_MATCH(Add(DontCare, lt, r1, r2, r7),
Add(DontCare, lt, r1, r7, r2));
// CHECK_SIZE_MATCH(Add(DontCare, r4, r4, r10),
// Add(DontCare, r4, r10, r4));
// CHECK_SIZE_MATCH(Add(DontCare, eq, r4, r4, r10),
// Add(DontCare, eq, r4, r10, r4));
// CHECK_SIZE_MATCH(Add(DontCare, r7, sp, r7),
// Add(DontCare, r7, r7, sp));
// CHECK_SIZE_MATCH(Add(DontCare, eq, r7, sp, r7),
// Add(DontCare, eq, r7, r7, sp));
// CHECK_SIZE_MATCH(Add(DontCare, sp, sp, r10),
// Add(DontCare, sp, r10, sp));
// CHECK_SIZE_MATCH(Add(DontCare, eq, sp, sp, r10),
// Add(DontCare, eq, sp, r10, sp));
// CHECK_SIZE_MATCH(And(DontCare, r7, r7, r6),
// And(DontCare, r7, r6, r7));
// CHECK_SIZE_MATCH(And(DontCare, eq, r7, r7, r6),
// And(DontCare, eq, r7, r6, r7));
// CHECK_SIZE_MATCH(Eor(DontCare, r7, r7, r6),
// Eor(DontCare, r7, r6, r7));
// CHECK_SIZE_MATCH(Eor(DontCare, eq, r7, r7, r6),
// Eor(DontCare, eq, r7, r6, r7));
// CHECK_SIZE_MATCH(Mul(DontCare, r0, r1, r0),
// Mul(DontCare, r0, r0, r1));
// CHECK_SIZE_MATCH(Mul(DontCare, eq, r0, r1, r0),
// Mul(DontCare, eq, r0, r0, r1));
// CHECK_SIZE_MATCH(Orr(DontCare, r7, r7, r6),
// Orr(DontCare, r7, r6, r7));
// CHECK_SIZE_MATCH(Orr(DontCare, eq, r7, r7, r6),
// Orr(DontCare, eq, r7, r6, r7));
CHECK_SIZE_MATCH(Adc(r7, r6, r7), Adc(r7, r7, r6));
// CHECK_SIZE_MATCH(Adc(eq, r7, r6, r7),
// Adc(eq, r7, r7, r6));
CHECK_SIZE_MATCH(Add(r1, r2, r7), Add(r1, r7, r2));
CHECK_SIZE_MATCH(Add(lt, r1, r2, r7), Add(lt, r1, r7, r2));
// CHECK_SIZE_MATCH(Add(r4, r4, r10),
// Add(r4, r10, r4));
// CHECK_SIZE_MATCH(Add(eq, r4, r4, r10),
// Add(eq, r4, r10, r4));
// CHECK_SIZE_MATCH(Add(r7, sp, r7),
// Add(r7, r7, sp));
// CHECK_SIZE_MATCH(Add(eq, r7, sp, r7),
// Add(eq, r7, r7, sp));
// CHECK_SIZE_MATCH(Add(sp, sp, r10),
// Add(sp, r10, sp));
// CHECK_SIZE_MATCH(Add(eq, sp, sp, r10),
// Add(eq, sp, r10, sp));
CHECK_SIZE_MATCH(And(r7, r7, r6), And(r7, r6, r7));
// CHECK_SIZE_MATCH(And(eq, r7, r7, r6),
// And(eq, r7, r6, r7));
CHECK_SIZE_MATCH(Eor(r7, r7, r6), Eor(r7, r6, r7));
// CHECK_SIZE_MATCH(Eor(eq, r7, r7, r6),
// Eor(eq, r7, r6, r7));
CHECK_SIZE_MATCH(Mul(r0, r1, r0), Mul(r0, r0, r1));
// CHECK_SIZE_MATCH(Mul(eq, r0, r1, r0),
// Mul(eq, r0, r0, r1));
CHECK_SIZE_MATCH(Orr(r7, r7, r6), Orr(r7, r6, r7));
// CHECK_SIZE_MATCH(Orr(eq, r7, r7, r6),
// Orr(eq, r7, r6, r7));
// CHECK_SIZE_MATCH(Adcs(r7, r6, r7),
// Adcs(r7, r7, r6));
// CHECK_SIZE_MATCH(Adcs(eq, r7, r6, r7),
// Adcs(eq, r7, r7, r6));
CHECK_SIZE_MATCH(Adds(r1, r2, r7), Adds(r1, r7, r2));
CHECK_SIZE_MATCH(Adds(lt, r1, r2, r7), Adds(lt, r1, r7, r2));
CHECK_SIZE_MATCH(Adds(r4, r4, r10), Adds(r4, r10, r4));
CHECK_SIZE_MATCH(Adds(eq, r4, r4, r10), Adds(eq, r4, r10, r4));
CHECK_SIZE_MATCH(Adds(r7, sp, r7), Adds(r7, r7, sp));
CHECK_SIZE_MATCH(Adds(eq, r7, sp, r7), Adds(eq, r7, r7, sp));
CHECK_SIZE_MATCH(Adds(sp, sp, r10), Adds(sp, r10, sp));
CHECK_SIZE_MATCH(Adds(eq, sp, sp, r10), Adds(eq, sp, r10, sp));
// CHECK_SIZE_MATCH(Ands(r7, r7, r6),
// Ands(r7, r6, r7));
// CHECK_SIZE_MATCH(Ands(eq, r7, r7, r6),
// Ands(eq, r7, r6, r7));
// CHECK_SIZE_MATCH(Eors(r7, r7, r6),
// Eors(r7, r6, r7));
// CHECK_SIZE_MATCH(Eors(eq, r7, r7, r6),
// Eors(eq, r7, r6, r7));
// CHECK_SIZE_MATCH(Muls(r0, r1, r0),
// Muls(r0, r0, r1));
// CHECK_SIZE_MATCH(Muls(eq, r0, r1, r0),
// Muls(eq, r0, r0, r1));
// CHECK_SIZE_MATCH(Orrs(r7, r7, r6),
// Orrs(r7, r6, r7));
// CHECK_SIZE_MATCH(Orrs(eq, r7, r7, r6),
// Orrs(eq, r7, r6, r7));
}
TEST(emit_pool_when_manually_placing_literal) {
SETUP();
START();
// Literal that will be manually placed.
Literal<uint64_t> l0(0xcafebeefdeadbaba, RawLiteral::kManuallyPlaced);
// Create one literal pool entry.
__ Ldrd(r0, r1, 0x1234567890abcdef);
// Branch using the assembler, to avoid introducing a veneer.
Label over_literal;
const int kBranchSize = 4;
{
ExactAssemblyScope scope(&masm,
kBranchSize,
ExactAssemblyScope::kExactSize);
__ b(&over_literal);
}
// Almost reach the pool checkpoint.
int32_t margin =
test.GetPoolCheckpoint() - masm.GetCursorOffset() - l0.GetSize() / 2;
int32_t end = masm.GetCursorOffset() + margin;
{
ExactAssemblyScope scope(&masm, margin, ExactAssemblyScope::kExactSize);
while (masm.GetCursorOffset() < end) {
__ nop();
}
}
VIXL_CHECK(!test.PoolIsEmpty());
__ Place(&l0);
// The pool must now have been emitted.
VIXL_CHECK(test.PoolIsEmpty());
__ Bind(&over_literal);
__ Ldrd(r2, r3, &l0);
END();
RUN();
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
ASSERT_EQUAL_32(0xdeadbaba, r2);
ASSERT_EQUAL_32(0xcafebeef, r3);
}
// The addition of padding only happens for T32.
TEST_T32(emit_pool_when_adding_padding_due_to_bind) {
SETUP();
START();
// Make sure we start with a 4-byte aligned address, in order for the
// location where we will call Bind() to be 4-byte aligned.
{
ExactAssemblyScope scope(&masm,
k16BitT32InstructionSizeInBytes,
ExactAssemblyScope::kMaximumSize);
while (masm.GetCursorOffset() % 4 != 0) {
__ nop();
}
}
// Create one literal pool entry.
__ Ldrd(r0, r1, 0x1234567890abcdef);
// Almost reach the pool checkpoint.
const int kPaddingBytes = 2;
int32_t margin =
test.GetPoolCheckpoint() - masm.GetCursorOffset() - kPaddingBytes;
int32_t end = masm.GetCursorOffset() + margin;
{
ExactAssemblyScope scope(&masm, margin, ExactAssemblyScope::kExactSize);
while (masm.GetCursorOffset() < end) {
__ nop();
}
}
Label label;
__ Cbz(r0, &label);
VIXL_CHECK(!test.PoolIsEmpty());
// In order to hit the case where binding the label needs to add padding,
// we need this to be a 4-byte aligned address.
VIXL_ASSERT((masm.GetBuffer()->GetCursorOffset() % 4) == 0);
__ Bind(&label);
// The pool must now have been emitted.
VIXL_CHECK(test.PoolIsEmpty());
END();
RUN();
ASSERT_EQUAL_32(0x90abcdef, r0);
ASSERT_EQUAL_32(0x12345678, r1);
}
static void AddBranchesAndGetCloseToCheckpoint(MacroAssembler* masm,
TestMacroAssembler* test,
const int kLabelsCount,
Label b_labels[],
int32_t margin) {
// Add many veneers to the pool.
for (int i = 0; i < kLabelsCount; i++) {
masm->B(&b_labels[i]);
}
// Get close to the veneer emission margin (considering the heuristic).
// Use add instead of nop to make viewing the disassembled code easier.
const int kAddSize = masm->IsUsingT32() ? k16BitT32InstructionSizeInBytes
: kA32InstructionSizeInBytes;
int32_t end = test->GetPoolCheckpoint();
int32_t space = end - masm->GetCursorOffset() - margin;
{
ExactAssemblyScope scope(masm, space, ExactAssemblyScope::kExactSize);
while (space > 0) {
masm->add(r0, r0, r0);
space -= kAddSize;
}
}
// Make sure the veneers have not yet been emitted.
const int kVeneerSize = 4;
VIXL_CHECK(test->GetPoolSize() == kLabelsCount * kVeneerSize);
}
static void EmitIndividualNops(MacroAssembler* masm, const int kNops) {
for (int i = 0; i < kNops; ++i) {
masm->Nop();
}
}
static void EmitNopsInExactAssemblyScope(MacroAssembler* masm,
const int kNops) {
const int kNopSize = masm->IsUsingT32() ? k16BitT32InstructionSizeInBytes
: kA32InstructionSizeInBytes;
{
ExactAssemblyScope scope(masm,
kNops * kNopSize,
ExactAssemblyScope::kExactSize);
for (int i = 0; i < kNops; i++) {
masm->nop();
}
}
}
TEST_A32(literal_and_veneer_interaction_1) {
SETUP();
START();
static const int kLabelsCount = 100;
Label b_labels[kLabelsCount];
AddBranchesAndGetCloseToCheckpoint(&masm,
&test,
kLabelsCount,
b_labels,
1 * KBytes);
// Emit a load of a large string. In the past, we have attempted to emit
// the literal load without emitting the veneers, which meant that we were
// left with an impossible scheduling problem for the pool objects (due to
// the short range of the ldrd).
std::string test_string(2 * KBytes, 'x');
StringLiteral big_literal(test_string.c_str());
__ Ldrd(r0, r1, &big_literal);
EmitIndividualNops(&masm, 1000);
// We can now safely bind the labels.
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&b_labels[i]);
}
END();
RUN();
}
TEST_A32(literal_and_veneer_interaction_2) {
SETUP();
START();
static const int kLabelsCount = 100;
Label b_labels[kLabelsCount];
AddBranchesAndGetCloseToCheckpoint(&masm,
&test,
kLabelsCount,
b_labels,
1 * KBytes);
// This is similar to the test above. The Ldr instruction can be emitted with
// no problems. The Ldrd used to force emission of the literal pool, pushing
// the veneers out of range - we make sure this does not happen anymore.
std::string test_string(2 * KBytes, 'z');
StringLiteral big_literal(test_string.c_str());
__ Ldr(r2, &big_literal);
const int kVeneerSize = 4;
CHECK_POOL_SIZE(kLabelsCount * kVeneerSize + big_literal.GetSize());
std::string test_string2(2 * KBytes, 'x');
StringLiteral big_literal2(test_string.c_str());
__ Ldrd(r0, r1, &big_literal2);
EmitIndividualNops(&masm, 1000);
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&b_labels[i]);
}
END();
RUN();
}
TEST_A32(literal_and_veneer_interaction_3) {
SETUP();
START();
static const int kLabelsCount = 100;
Label b_labels[kLabelsCount];
AddBranchesAndGetCloseToCheckpoint(&masm,
&test,
kLabelsCount,
b_labels,
1 * KBytes);
// Here, we used to emit the Ldrd instruction and then emit the veneers
// before the literal is emitted, hence pushing the Ldrd out of range.
// Make sure this does not happen anymore.
__ Ldrd(r2, r3, 0x12345678);
// The issue would only appear when emitting the nops in a single scope.
EmitNopsInExactAssemblyScope(&masm, 4096);
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&b_labels[i]);
}
END();
RUN();
}
// Equivalent to literal_and_veneer_interaction_1, but for T32.
TEST_T32(literal_and_veneer_interaction_4) {
SETUP();
START();
static const int kLabelsCount = 550;
Label b_labels[kLabelsCount];
AddBranchesAndGetCloseToCheckpoint(&masm,
&test,
kLabelsCount,
b_labels,
KBytes / 2);
std::string test_string(3 * KBytes, 'x');
StringLiteral big_literal(test_string.c_str());
__ Ldrd(r0, r1, &big_literal);
EmitIndividualNops(&masm, 2000);
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&b_labels[i]);
}
END();
RUN();
}
// Equivalent to literal_and_veneer_interaction_3, but for T32.
TEST_T32(literal_and_veneer_interaction_5) {
SETUP();
START();
static const int kLabelsCount = 550;
Label b_labels[kLabelsCount];
AddBranchesAndGetCloseToCheckpoint(&masm,
&test,
kLabelsCount,
b_labels,
1 * KBytes);
__ Ldrd(r2, r3, 0x12345678);
EmitNopsInExactAssemblyScope(&masm, 4096);
for (int i = 0; i < kLabelsCount; i++) {
__ Bind(&b_labels[i]);
}
END();
RUN();
}
TEST_T32(assembler_bind_label) {
SETUP();
START();
Label label;
__ B(eq, &label, kNear);
// At this point we keep track of the veneer in the pool.
VIXL_CHECK(!test.PoolIsEmpty());
{
// Bind the label with the assembler.
ExactAssemblyScope scope(&masm, 2, ExactAssemblyScope::kMaximumSize);
__ bind(&label);
}
// Make sure the pool is now empty.
VIXL_CHECK(test.PoolIsEmpty());
EmitNopsInExactAssemblyScope(&masm, 4096);
END();
RUN();
}
#ifdef VIXL_DEBUG
#define TEST_FORWARD_REFERENCE_INFO(INST, INFO, ASM) \
POSITIVE_TEST_FORWARD_REFERENCE_INFO(INST, INFO, ASM) \
NEGATIVE_TEST_FORWARD_REFERENCE_INFO(INST, ASM)
#else
// Skip the negative tests for release builds, as they require debug-only checks
// in ExactAssemblyScope.
#define TEST_FORWARD_REFERENCE_INFO(INST, INFO, ASM) \
POSITIVE_TEST_FORWARD_REFERENCE_INFO(INST, INFO, ASM)
#endif
#define POSITIVE_TEST_FORWARD_REFERENCE_INFO(INST, INFO, ASM) \
can_encode = masm.INFO; \
VIXL_CHECK(can_encode); \
{ \
ExactAssemblyScope scope(&masm, \
info->size, \
ExactAssemblyScope::kExactSize); \
int32_t pc = masm.GetCursorOffset() + __ GetArchitectureStatePCOffset(); \
if (info->pc_needs_aligning == ReferenceInfo::kAlignPc) { \
pc = AlignDown(pc, 4); \
} \
Label label(pc + info->min_offset); \
masm.ASM; \
} \
{ \
ExactAssemblyScope scope(&masm, \
info->size, \
ExactAssemblyScope::kExactSize); \
int32_t pc = masm.GetCursorOffset() + __ GetArchitectureStatePCOffset(); \
if (info->pc_needs_aligning == ReferenceInfo::kAlignPc) { \
pc = AlignDown(pc, 4); \
} \
Label label(pc + info->max_offset); \
masm.ASM; \
}
#ifdef VIXL_NEGATIVE_TESTING
#define NEGATIVE_TEST_FORWARD_REFERENCE_INFO(INST, ASM) \
try { \
ExactAssemblyScope scope(&masm, \
info->size, \
ExactAssemblyScope::kMaximumSize); \
int32_t pc = masm.GetCursorOffset() + __ GetArchitectureStatePCOffset(); \
if (info->pc_needs_aligning == ReferenceInfo::kAlignPc) { \
pc = AlignDown(pc, 4); \
} \
Label label(pc + info->max_offset + info->alignment); \
masm.ASM; \
printf("Negative test for forward reference failed for %s.\n", INST); \
abort(); \
} catch (const std::runtime_error&) { \
} \
try { \
ExactAssemblyScope scope(&masm, \
info->size, \
ExactAssemblyScope::kMaximumSize); \
int32_t pc = masm.GetCursorOffset() + __ GetArchitectureStatePCOffset(); \
if (info->pc_needs_aligning == ReferenceInfo::kAlignPc) { \
pc = AlignDown(pc, 4); \
} \
Label label(pc + info->min_offset - info->alignment); \
masm.ASM; \
printf("Negative test for forward reference failed for %s.\n", INST); \
abort(); \
} catch (const std::runtime_error&) { \
}
#else
#define NEGATIVE_TEST_FORWARD_REFERENCE_INFO(INST, ASM)
#endif
TEST_T32(forward_reference_info_T32) {
MacroAssembler masm(BUF_SIZE, T32);
Label unbound;
const ReferenceInfo* info;
bool can_encode;
// clang-format off
TEST_FORWARD_REFERENCE_INFO(
"adr",
adr_info(al, Narrow, r0, &unbound, &info),
adr(al, Narrow, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"adr",
adr_info(al, Wide, r0, &unbound, &info),
adr(al, Wide, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"adr",
adr_info(al, Best, r0, &unbound, &info),
adr(al, Best, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"b",
b_info(al, Narrow, &unbound, &info),
b(al, Narrow, &label));
TEST_FORWARD_REFERENCE_INFO(
"b",
b_info(al, Wide, &unbound, &info),
b(al, Wide, &label));
TEST_FORWARD_REFERENCE_INFO(
"b",
b_info(al, Best, &unbound, &info),
b(al, Best, &label));
TEST_FORWARD_REFERENCE_INFO(
"b",
b_info(gt, Narrow, &unbound, &info),
b(gt, Narrow, &label));
TEST_FORWARD_REFERENCE_INFO(
"b",
b_info(gt, Wide, &unbound, &info),
b(gt, Wide, &label));
TEST_FORWARD_REFERENCE_INFO(
"b",
b_info(gt, Best, &unbound, &info),
b(gt, Best, &label));
TEST_FORWARD_REFERENCE_INFO(
"bl",
bl_info(al, &unbound, &info),
bl(al, &label));
TEST_FORWARD_REFERENCE_INFO(
"blx",
blx_info(al, &unbound, &info),
blx(al, &label));
TEST_FORWARD_REFERENCE_INFO(
"cbnz",
cbnz_info(r0, &unbound, &info),
cbnz(r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"cbz",
cbz_info(r0, &unbound, &info),
cbz(r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldr",
ldr_info(al, Narrow, r0, &unbound, &info),
ldr(al, Narrow, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldr",
ldr_info(al, Wide, r0, &unbound, &info),
ldr(al, Wide, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldr",
ldr_info(al, Best, r0, &unbound, &info),
ldr(al, Best, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrb",
ldrb_info(al, r0, &unbound, &info),
ldrb(al, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrd",
ldrd_info(al, r0, r1, &unbound, &info),
ldrd(al, r0, r1, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrh",
ldrh_info(al, r0, &unbound, &info),
ldrh(al, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrsb",
ldrsb_info(al, r0, &unbound, &info),
ldrsb(al, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrsh",
ldrsh_info(al, r0, &unbound, &info),
ldrsh(al, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"pld",
pld_info(al, &unbound, &info),
pld(al, &label));
TEST_FORWARD_REFERENCE_INFO(
"pli",
pli_info(al, &unbound, &info),
pli(al, &label));
TEST_FORWARD_REFERENCE_INFO(
"vldr",
vldr_info(al, Untyped64, d0, &unbound, &info),
vldr(al, Untyped64, d0, &label));
TEST_FORWARD_REFERENCE_INFO(
"vldr",
vldr_info(al, Untyped32, s0, &unbound, &info),
vldr(al, Untyped32, s0, &label));
// clang-format on
masm.FinalizeCode();
}
TEST_A32(forward_reference_info_A32) {
MacroAssembler masm(BUF_SIZE, A32);
Label unbound;
const ReferenceInfo* info;
bool can_encode;
// clang-format off
TEST_FORWARD_REFERENCE_INFO(
"adr",
adr_info(al, Best, r0, &unbound, &info),
adr(al, Best, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"b",
b_info(al, Best, &unbound, &info),
b(al, Best, &label));
TEST_FORWARD_REFERENCE_INFO(
"b",
b_info(gt, Best, &unbound, &info),
b(gt, Best, &label));
TEST_FORWARD_REFERENCE_INFO(
"bl",
bl_info(al, &unbound, &info),
bl(al, &label));
TEST_FORWARD_REFERENCE_INFO(
"blx",
blx_info(al, &unbound, &info),
blx(al, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldr",
ldr_info(al, Best, r0, &unbound, &info),
ldr(al, Best, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrb",
ldrb_info(al, r0, &unbound, &info),
ldrb(al, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrd",
ldrd_info(al, r0, r1, &unbound, &info),
ldrd(al, r0, r1, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrh",
ldrh_info(al, r0, &unbound, &info),
ldrh(al, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrsb",
ldrsb_info(al, r0, &unbound, &info),
ldrsb(al, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"ldrsh",
ldrsh_info(al, r0, &unbound, &info),
ldrsh(al, r0, &label));
TEST_FORWARD_REFERENCE_INFO(
"pld",
pld_info(al, &unbound, &info),
pld(al, &label));
TEST_FORWARD_REFERENCE_INFO(
"pli",
pli_info(al, &unbound, &info),
pli(al, &label));
TEST_FORWARD_REFERENCE_INFO(
"vldr",
vldr_info(al, Untyped64, d0, &unbound, &info),
vldr(al, Untyped64, d0, &label));
TEST_FORWARD_REFERENCE_INFO(
"vldr",
vldr_info(al, Untyped32, s0, &unbound, &info),
vldr(al, Untyped32, s0, &label));
// clang-format on
masm.FinalizeCode();
}
} // namespace aarch32
} // namespace vixl