diff --git a/CMakeLists.txt b/CMakeLists.txt index 34dbcbb3..8d435469 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -3,6 +3,7 @@ project(dynarmic) # Dynarmic project options option(DYNARMIC_USE_SYSTEM_BOOST "Use the system boost libraries" ON) +option(DYNARMIC_USE_LLVM "Support disassembly of jitted x86_64 code using LLVM" OFF) # Compiler flags if (NOT MSVC) @@ -81,6 +82,14 @@ include_directories(${Boost_INCLUDE_DIRS}) include_directories(externals/catch) enable_testing(true) # Enables unit-testing. +# Include LLVM +if (DYNARMIC_USE_LLVM) + find_package(LLVM REQUIRED CONFIG) + include_directories(${LLVM_INCLUDE_DIRS}) + add_definitions(-DDYNARMIC_USE_LLVM ${LLVM_DEFINITIONS}) + llvm_map_components_to_libnames(llvm_libs x86desc x86disassembler) +endif() + # Dynarmic project files add_subdirectory(src) add_subdirectory(tests) diff --git a/src/backend_x64/emit_x64.cpp b/src/backend_x64/emit_x64.cpp index 1bfe3a0f..76afd2a3 100644 --- a/src/backend_x64/emit_x64.cpp +++ b/src/backend_x64/emit_x64.cpp @@ -48,13 +48,13 @@ static void EraseInstruction(IR::Block& block, IR::Inst* inst) { block.instructions.erase(block.instructions.iterator_to(*inst)); } -CodePtr EmitX64::Emit(const Arm::LocationDescriptor descriptor, Dynarmic::IR::Block& block) { +EmitX64::BlockDescriptor* EmitX64::Emit(const Arm::LocationDescriptor descriptor, Dynarmic::IR::Block& block) { inhibit_emission.clear(); reg_alloc.Reset(); code->INT3(); CodePtr code_ptr = code->GetCodePtr(); - basic_blocks[descriptor] = code_ptr; + basic_blocks[descriptor].code_ptr = code_ptr; EmitCondPrelude(block.cond, block.cond_failed, block.location); @@ -84,7 +84,8 @@ CodePtr EmitX64::Emit(const Arm::LocationDescriptor descriptor, Dynarmic::IR::Bl reg_alloc.AssertNoMoreUses(); - return code_ptr; + basic_blocks[descriptor].size = code->GetCodePtr() - code_ptr; + return &basic_blocks[descriptor]; } void EmitX64::EmitBreakpoint(IR::Block&, IR::Inst*) { @@ -276,6 +277,24 @@ void EmitX64::EmitGetOverflowFromOp(IR::Block&, IR::Inst*) { ASSERT_MSG(0, "should never happen"); } +void EmitX64::EmitPack2x32To1x64(IR::Block&, IR::Inst* inst) { + auto lo = reg_alloc.UseRegister(inst->GetArg(0), any_gpr); + auto hi = reg_alloc.UseDefRegister(inst->GetArg(1), inst, any_gpr); + code->SHL(64, R(hi), Imm8(32)); + code->OR(64, R(hi), R(lo)); +} + +void EmitX64::EmitLeastSignificantWord(IR::Block&, IR::Inst* inst) { + // TODO: Optimize + auto u64 = reg_alloc.UseDefRegister(inst->GetArg(0), inst, any_gpr); + code->MOVZX(64, 32, u64, R(u64)); +} + +void EmitX64::EmitMostSignificantWord(IR::Block&, IR::Inst* inst) { + auto u64 = reg_alloc.UseDefRegister(inst->GetArg(0), inst, any_gpr); + code->SHR(64, R(u64), Imm8(32)); +} + void EmitX64::EmitLeastSignificantHalf(IR::Block&, IR::Inst* inst) { reg_alloc.RegisterAddDef(inst, inst->GetArg(0)); } @@ -302,6 +321,16 @@ void EmitX64::EmitIsZero(IR::Block&, IR::Inst* inst) { code->MOVZX(32, 8, result, R(result)); } +void EmitX64::EmitIsZero64(IR::Block&, IR::Inst* inst) { + X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst, any_gpr); + + // TODO: Flag optimization + + code->TEST(64, R(result), R(result)); + code->SETcc(CCFlags::CC_E, R(result)); + code->MOVZX(32, 8, result, R(result)); +} + void EmitX64::EmitLogicalShiftLeft(IR::Block& block, IR::Inst* inst) { auto carry_inst = FindUseWithOpcode(inst, IR::Opcode::GetCarryFromOp); @@ -704,6 +733,16 @@ void EmitX64::EmitAddWithCarry(IR::Block& block, IR::Inst* inst) { } } +void EmitX64::EmitAdd64(IR::Block& block, IR::Inst* inst) { + IR::Value a = inst->GetArg(0); + IR::Value b = inst->GetArg(1); + + X64Reg result = reg_alloc.UseDefRegister(a, inst, any_gpr); + OpArg op_arg = R(reg_alloc.UseRegister(b, any_gpr)); + + code->ADD(64, R(result), op_arg); +} + void EmitX64::EmitSubWithCarry(IR::Block& block, IR::Inst* inst) { auto carry_inst = FindUseWithOpcode(inst, IR::Opcode::GetCarryFromOp); auto overflow_inst = FindUseWithOpcode(inst, IR::Opcode::GetOverflowFromOp); @@ -747,6 +786,28 @@ void EmitX64::EmitSubWithCarry(IR::Block& block, IR::Inst* inst) { } } +void EmitX64::EmitMul(IR::Block&, IR::Inst* inst) { + IR::Value a = inst->GetArg(0); + IR::Value b = inst->GetArg(1); + if (a.IsImmediate()) + std::swap(a, b); + X64Reg result = reg_alloc.UseDefRegister(a, inst, any_gpr); + if (b.IsImmediate()) { + code->IMUL(32, result, R(result), Imm32(b.GetU32())); + } else { + OpArg op_arg = R(reg_alloc.UseRegister(b.GetInst(), any_gpr)); + code->IMUL(32, result, op_arg); + } +} + +void EmitX64::EmitMul64(IR::Block&, IR::Inst* inst) { + IR::Value a = inst->GetArg(0); + IR::Value b = inst->GetArg(1); + X64Reg result = reg_alloc.UseDefRegister(a, inst, any_gpr); + OpArg op_arg = R(reg_alloc.UseRegister(b.GetInst(), any_gpr)); + code->IMUL(64, result, op_arg); +} + void EmitX64::EmitAnd(IR::Block&, IR::Inst* inst) { IR::Value a = inst->GetArg(0); IR::Value b = inst->GetArg(1); @@ -791,6 +852,13 @@ void EmitX64::EmitNot(IR::Block&, IR::Inst* inst) { } } +void EmitX64::EmitSignExtendWordToLong(IR::Block&, IR::Inst* inst) { + // TODO: Remove unnecessary mov that may occur here + X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst, any_gpr); + + code->MOVSX(64, 32, result, R(result)); +} + void EmitX64::EmitSignExtendHalfToWord(IR::Block&, IR::Inst* inst) { OpArg source; X64Reg result; @@ -807,6 +875,13 @@ void EmitX64::EmitSignExtendByteToWord(IR::Block&, IR::Inst* inst) { code->MOVSX(32, 8, result, source); } +void EmitX64::EmitZeroExtendWordToLong(IR::Block&, IR::Inst* inst) { + // TODO: Remove unnecessary mov that may occur here + X64Reg result = reg_alloc.UseDefRegister(inst->GetArg(0), inst, any_gpr); + + code->MOVZX(64, 32, result, R(result)); +} + void EmitX64::EmitZeroExtendHalfToWord(IR::Block&, IR::Inst* inst) { OpArg source; X64Reg result; diff --git a/src/backend_x64/emit_x64.h b/src/backend_x64/emit_x64.h index 809464ff..51ac3ff2 100644 --- a/src/backend_x64/emit_x64.h +++ b/src/backend_x64/emit_x64.h @@ -23,11 +23,15 @@ public: EmitX64(Gen::XEmitter* code, Routines* routines, UserCallbacks cb, Jit* jit_interface) : reg_alloc(code), code(code), routines(routines), cb(cb), jit_interface(jit_interface) {} - CodePtr Emit(const Arm::LocationDescriptor descriptor, IR::Block& ir); + struct BlockDescriptor { + CodePtr code_ptr; + size_t size; + }; + BlockDescriptor* Emit(const Arm::LocationDescriptor descriptor, IR::Block& ir); - CodePtr GetBasicBlock(Arm::LocationDescriptor descriptor) { + BlockDescriptor* GetBasicBlock(Arm::LocationDescriptor descriptor) { auto iter = basic_blocks.find(descriptor); - return iter != basic_blocks.end() ? iter->second : nullptr; + return iter != basic_blocks.end() ? &iter->second : nullptr; } void ClearCache(); @@ -62,7 +66,7 @@ private: Routines* routines; UserCallbacks cb; Jit* jit_interface; - std::unordered_map basic_blocks; + std::unordered_map basic_blocks; }; } // namespace BackendX64 diff --git a/src/backend_x64/interface_x64.cpp b/src/backend_x64/interface_x64.cpp index 18679cb4..0e3ba207 100644 --- a/src/backend_x64/interface_x64.cpp +++ b/src/backend_x64/interface_x64.cpp @@ -6,6 +6,11 @@ #include +#ifdef DYNARMIC_USE_LLVM +#include +#include +#endif + #include "backend_x64/emit_x64.h" #include "backend_x64/jitstate.h" #include "backend_x64/routines.h" @@ -13,6 +18,7 @@ #include "common/bit_util.h" #include "common/common_types.h" #include "common/scope_exit.h" +#include "common/string_util.h" #include "frontend/arm_types.h" #include "frontend/translate/translate.h" #include "interface/interface.h" @@ -44,14 +50,52 @@ struct Jit::Impl { Arm::LocationDescriptor descriptor{pc, TFlag, EFlag, jit_state.Fpscr}; - CodePtr code_ptr = GetBasicBlock(descriptor); + CodePtr code_ptr = GetBasicBlock(descriptor)->code_ptr; return routines.RunCode(&jit_state, code_ptr, cycle_count); } + + std::string Disassemble(Arm::LocationDescriptor descriptor) { + auto block = GetBasicBlock(descriptor); + std::string result = Common::StringFromFormat("address: %p\nsize: %zu bytes\n", block->code_ptr, block->size); + +#ifdef DYNARMIC_USE_LLVM + CodePtr end = block->code_ptr + block->size; + size_t remaining = block->size; + + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86TargetMC(); + LLVMInitializeX86Disassembler(); + LLVMDisasmContextRef llvm_ctx = LLVMCreateDisasm("x86_64", nullptr, 0, nullptr, nullptr); + LLVMSetDisasmOptions(llvm_ctx, LLVMDisassembler_Option_AsmPrinterVariant); + + for (CodePtr pos = block->code_ptr; pos < end;) { + char buffer[80]; + size_t inst_size = LLVMDisasmInstruction(llvm_ctx, const_cast(pos), remaining, (u64)pos, buffer, sizeof(buffer)); + assert(inst_size); + for (CodePtr i = pos; i < pos + inst_size; i++) + result.append(Common::StringFromFormat("%02x ", *i)); + for (size_t i = inst_size; i < 10; i++) + result.append(" "); + result.append(buffer); + result.append("\n"); + + pos += inst_size; + remaining -= inst_size; + } + + LLVMDisasmDispose(llvm_ctx); +#else + result.append("(recompile with DYNARMIC_USE_LLVM=ON to disassemble the generated x86_64 code)\n"); +#endif + + return result; + } + private: - CodePtr GetBasicBlock(Arm::LocationDescriptor descriptor) { - CodePtr code_ptr = emitter.GetBasicBlock(descriptor); - if (code_ptr) - return code_ptr; + EmitX64::BlockDescriptor* GetBasicBlock(Arm::LocationDescriptor descriptor) { + auto block = emitter.GetBasicBlock(descriptor); + if (block) + return block; IR::Block ir_block = Arm::Translate(descriptor, callbacks.MemoryRead32); Optimization::GetSetElimination(ir_block); @@ -113,4 +157,8 @@ u32 Jit::Cpsr() const { return impl->jit_state.Cpsr; } +std::string Jit::Disassemble(Arm::LocationDescriptor descriptor) { + return impl->Disassemble(descriptor); +} + } // namespace Dynarmic diff --git a/src/frontend/decoder/arm.h b/src/frontend/decoder/arm.h index 0367b8d8..15cfa982 100644 --- a/src/frontend/decoder/arm.h +++ b/src/frontend/decoder/arm.h @@ -246,15 +246,15 @@ boost::optional&> DecodeArm(u32 instruction) { //INST(&V::arm_USAT16, "USAT16", "cccc01101110vvvvdddd11110011nnnn"), // v6 // Multiply (Normal) instructions - //INST(&V::arm_MLA, "MLA", "cccc0000001Sddddaaaammmm1001nnnn"), // v2 - //INST(&V::arm_MUL, "MUL", "cccc0000000Sdddd0000mmmm1001nnnn"), // v2 + INST(&V::arm_MLA, "MLA", "cccc0000001Sddddaaaammmm1001nnnn"), // v2 + INST(&V::arm_MUL, "MUL", "cccc0000000Sdddd0000mmmm1001nnnn"), // v2 // Multiply (Long) instructions - //INST(&V::arm_SMLAL, "SMLAL", "cccc0000111Sddddaaaammmm1001nnnn"), // v3M - //INST(&V::arm_SMULL, "SMULL", "cccc0000110Sddddaaaammmm1001nnnn"), // v3M - //INST(&V::arm_UMAAL, "UMAAL", "cccc00000100ddddaaaammmm1001nnnn"), // v6 - //INST(&V::arm_UMLAL, "UMLAL", "cccc0000101Sddddaaaammmm1001nnnn"), // v3M - //INST(&V::arm_UMULL, "UMULL", "cccc0000100Sddddaaaammmm1001nnnn"), // v3M + INST(&V::arm_SMLAL, "SMLAL", "cccc0000111Sddddaaaammmm1001nnnn"), // v3M + INST(&V::arm_SMULL, "SMULL", "cccc0000110Sddddaaaammmm1001nnnn"), // v3M + INST(&V::arm_UMAAL, "UMAAL", "cccc00000100ddddaaaammmm1001nnnn"), // v6 + INST(&V::arm_UMLAL, "UMLAL", "cccc0000101Sddddaaaammmm1001nnnn"), // v3M + INST(&V::arm_UMULL, "UMULL", "cccc0000100Sddddaaaammmm1001nnnn"), // v3M // Multiply (Halfword) instructions //INST(&V::arm_SMLALxy, "SMLALXY", "cccc00010100ddddaaaammmm1xy0nnnn"), // v5xP diff --git a/src/frontend/disassembler/disassembler_arm.cpp b/src/frontend/disassembler/disassembler_arm.cpp index 4a086ab7..eb813f15 100644 --- a/src/frontend/disassembler/disassembler_arm.cpp +++ b/src/frontend/disassembler/disassembler_arm.cpp @@ -383,15 +383,29 @@ public: std::string arm_USAT16(Cond cond, Imm4 sat_imm, Reg d, Reg n) { return "ice"; } // Multiply (Normal) instructions - std::string arm_MLA(Cond cond, bool S, Reg d, Reg a, Reg m, Reg n) { return "ice"; } - std::string arm_MUL(Cond cond, bool S, Reg d, Reg m, Reg n) { return "ice"; } + std::string arm_MLA(Cond cond, bool S, Reg d, Reg a, Reg m, Reg n) { + return Common::StringFromFormat("mla%s%s %s, %s, %s, %s", S ? "s" : "", CondToString(cond), RegToString(d), RegToString(n), RegToString(m), RegToString(a)); + } + std::string arm_MUL(Cond cond, bool S, Reg d, Reg m, Reg n) { + return Common::StringFromFormat("mul%s%s %s, %s, %s", S ? "s" : "", CondToString(cond), RegToString(d), RegToString(n), RegToString(m)); + } // Multiply (Long) instructions - std::string arm_SMLAL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { return "ice"; } - std::string arm_SMULL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { return "ice"; } - std::string arm_UMAAL(Cond cond, Reg dHi, Reg dLo, Reg m, Reg n) { return "ice"; } - std::string arm_UMLAL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { return "ice"; } - std::string arm_UMULL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { return "ice"; } + std::string arm_SMLAL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { + return Common::StringFromFormat("smlal%s%s %s, %s, %s, %s", S ? "s" : "", CondToString(cond), RegToString(dLo), RegToString(dHi), RegToString(n), RegToString(m)); + } + std::string arm_SMULL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { + return Common::StringFromFormat("smull%s%s %s, %s, %s, %s", S ? "s" : "", CondToString(cond), RegToString(dLo), RegToString(dHi), RegToString(n), RegToString(m)); + } + std::string arm_UMAAL(Cond cond, Reg dHi, Reg dLo, Reg m, Reg n) { + return Common::StringFromFormat("umaal%s %s, %s, %s, %s", CondToString(cond), RegToString(dLo), RegToString(dHi), RegToString(n), RegToString(m)); + } + std::string arm_UMLAL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { + return Common::StringFromFormat("umlal%s%s %s, %s, %s, %s", S ? "s" : "", CondToString(cond), RegToString(dLo), RegToString(dHi), RegToString(n), RegToString(m)); + } + std::string arm_UMULL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { + return Common::StringFromFormat("umull%s%s %s, %s, %s, %s", S ? "s" : "", CondToString(cond), RegToString(dLo), RegToString(dHi), RegToString(n), RegToString(m)); + } // Multiply (Halfword) instructions std::string arm_SMLALxy(Cond cond, Reg dHi, Reg dLo, Reg m, bool M, bool N, Reg n) { return "ice"; } diff --git a/src/frontend/ir/ir_emitter.cpp b/src/frontend/ir/ir_emitter.cpp index 037f5992..2182c1bb 100644 --- a/src/frontend/ir/ir_emitter.cpp +++ b/src/frontend/ir/ir_emitter.cpp @@ -98,6 +98,19 @@ void IREmitter::SetVFlag(const IR::Value& value) { Inst(IR::Opcode::SetVFlag, {value}); } +IR::Value IREmitter::Pack2x32To1x64(const IR::Value& lo, const IR::Value& hi) +{ + return Inst(IR::Opcode::Pack2x32To1x64, {lo, hi}); +} + +IR::Value IREmitter::LeastSignificantWord(const IR::Value& value) { + return Inst(IR::Opcode::LeastSignificantWord, {value}); +} + +IR::Value IREmitter::MostSignificantWord(const IR::Value& value) { + return Inst(IR::Opcode::MostSignificantWord, {value}); +} + IR::Value IREmitter::LeastSignificantHalf(const IR::Value& value) { return Inst(IR::Opcode::LeastSignificantHalf, {value}); } @@ -114,6 +127,10 @@ IR::Value IREmitter::IsZero(const IR::Value& value) { return Inst(IR::Opcode::IsZero, {value}); } +IR::Value IREmitter::IsZero64(const IR::Value& value) { + return Inst(IR::Opcode::IsZero64, {value}); +} + IREmitter::ResultAndCarry IREmitter::LogicalShiftLeft(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in) { auto result = Inst(IR::Opcode::LogicalShiftLeft, {value_in, shift_amount, carry_in}); auto carry_out = Inst(IR::Opcode::GetCarryFromOp, {result}); @@ -155,6 +172,10 @@ IR::Value IREmitter::Add(const IR::Value& a, const IR::Value& b) { return Inst(IR::Opcode::AddWithCarry, {a, b, Imm1(0)}); } +IR::Value IREmitter::Add64(const IR::Value& a, const IR::Value& b) { + return Inst(IR::Opcode::Add64, {a, b}); +} + IREmitter::ResultAndCarryAndOverflow IREmitter::SubWithCarry(const IR::Value& a, const IR::Value& b, const IR::Value& carry_in) { // This is equivalent to AddWithCarry(a, Not(b), carry_in). auto result = Inst(IR::Opcode::SubWithCarry, {a, b, carry_in}); @@ -167,6 +188,28 @@ IR::Value IREmitter::Sub(const IR::Value& a, const IR::Value& b) { return Inst(IR::Opcode::SubWithCarry, {a, b, Imm1(1)}); } +IR::Value IREmitter::Mul(const IR::Value& a, const IR::Value& b) { + return Inst(IR::Opcode::Mul, {a, b}); +} + +IR::Value IREmitter::Mul64(const IR::Value& a, const IR::Value& b) { + return Inst(IR::Opcode::Mul64, {a, b}); +} + +IR::Value IREmitter::SignedMulHi(const IR::Value& a, const IR::Value& b) { + auto a64 = ZeroExtendWordToLong(a); + auto b64 = ZeroExtendWordToLong(b); + auto product64 = Mul64(a64, b64); + return LogicalShiftRight(product64, Imm8(32), Imm8(0)).result; +} + +IR::Value IREmitter::UnsignedMulHi(const IR::Value& a, const IR::Value& b) { + auto a64 = SignExtendWordToLong(a); + auto b64 = SignExtendWordToLong(b); + auto product64 = Mul64(a64, b64); + return LogicalShiftRight(product64, Imm8(32), Imm8(0)).result; +} + IR::Value IREmitter::And(const IR::Value& a, const IR::Value& b) { return Inst(IR::Opcode::And, {a, b}); } @@ -183,6 +226,10 @@ IR::Value IREmitter::Not(const IR::Value& a) { return Inst(IR::Opcode::Not, {a}); } +IR::Value IREmitter::SignExtendWordToLong(const IR::Value& a) { + return Inst(IR::Opcode::SignExtendWordToLong, {a}); +} + IR::Value IREmitter::SignExtendHalfToWord(const IR::Value& a) { return Inst(IR::Opcode::SignExtendHalfToWord, {a}); } @@ -191,6 +238,10 @@ IR::Value IREmitter::SignExtendByteToWord(const IR::Value& a) { return Inst(IR::Opcode::SignExtendByteToWord, {a}); } +IR::Value IREmitter::ZeroExtendWordToLong(const IR::Value& a) { + return Inst(IR::Opcode::ZeroExtendWordToLong, {a}); +} + IR::Value IREmitter::ZeroExtendHalfToWord(const IR::Value& a) { return Inst(IR::Opcode::ZeroExtendHalfToWord, {a}); } diff --git a/src/frontend/ir/ir_emitter.h b/src/frontend/ir/ir_emitter.h index 41b9a2e2..a2e6cfc7 100644 --- a/src/frontend/ir/ir_emitter.h +++ b/src/frontend/ir/ir_emitter.h @@ -54,10 +54,14 @@ public: void SetCFlag(const IR::Value& value); void SetVFlag(const IR::Value& value); + IR::Value Pack2x32To1x64(const IR::Value& lo, const IR::Value& hi); + IR::Value LeastSignificantWord(const IR::Value& value); + IR::Value MostSignificantWord(const IR::Value& value); IR::Value LeastSignificantHalf(const IR::Value& value); IR::Value LeastSignificantByte(const IR::Value& value); IR::Value MostSignificantBit(const IR::Value& value); IR::Value IsZero(const IR::Value& value); + IR::Value IsZero64(const IR::Value& value); ResultAndCarry LogicalShiftLeft(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in); ResultAndCarry LogicalShiftRight(const IR::Value& value_in, const IR::Value& shift_amount, const IR::Value& carry_in); @@ -66,14 +70,21 @@ public: ResultAndCarry RotateRightExtended(const IR::Value& value_in, const IR::Value& carry_in); ResultAndCarryAndOverflow AddWithCarry(const IR::Value& a, const IR::Value& b, const IR::Value& carry_in); IR::Value Add(const IR::Value& a, const IR::Value& b); + IR::Value Add64(const IR::Value& a, const IR::Value& b); ResultAndCarryAndOverflow SubWithCarry(const IR::Value& a, const IR::Value& b, const IR::Value& carry_in); IR::Value Sub(const IR::Value& a, const IR::Value& b); + IR::Value Mul(const IR::Value& a, const IR::Value& b); + IR::Value Mul64(const IR::Value& a, const IR::Value& b); + IR::Value SignedMulHi(const IR::Value& a, const IR::Value& b); + IR::Value UnsignedMulHi(const IR::Value& a, const IR::Value& b); IR::Value And(const IR::Value& a, const IR::Value& b); IR::Value Eor(const IR::Value& a, const IR::Value& b); IR::Value Or(const IR::Value& a, const IR::Value& b); IR::Value Not(const IR::Value& a); + IR::Value SignExtendWordToLong(const IR::Value& a); IR::Value SignExtendHalfToWord(const IR::Value& a); IR::Value SignExtendByteToWord(const IR::Value& a); + IR::Value ZeroExtendWordToLong(const IR::Value& a); IR::Value ZeroExtendHalfToWord(const IR::Value& a); IR::Value ZeroExtendByteToWord(const IR::Value& a); IR::Value ByteReverseWord(const IR::Value& a); diff --git a/src/frontend/ir/opcodes.inc b/src/frontend/ir/opcodes.inc index fc9ff984..03c7d121 100644 --- a/src/frontend/ir/opcodes.inc +++ b/src/frontend/ir/opcodes.inc @@ -22,10 +22,14 @@ OPCODE(GetCarryFromOp, T::U1, T::U32 OPCODE(GetOverflowFromOp, T::U1, T::U32 ) // Calculations +OPCODE(Pack2x32To1x64, T::U64, T::U32, T::U32 ) +OPCODE(LeastSignificantWord, T::U32, T::U64 ) +OPCODE(MostSignificantWord, T::U32, T::U64 ) OPCODE(LeastSignificantHalf, T::U16, T::U32 ) OPCODE(LeastSignificantByte, T::U8, T::U32 ) OPCODE(MostSignificantBit, T::U1, T::U32 ) OPCODE(IsZero, T::U1, T::U32 ) +OPCODE(IsZero64, T::U1, T::U64 ) OPCODE(LogicalShiftLeft, T::U32, T::U32, T::U8, T::U1 ) OPCODE(LogicalShiftRight, T::U32, T::U32, T::U8, T::U1 ) OPCODE(ArithmeticShiftRight, T::U32, T::U32, T::U8, T::U1 ) @@ -33,12 +37,17 @@ OPCODE(RotateRight, T::U32, T::U32, T::U8, OPCODE(RotateRightExtended, T::U32, T::U32, T::U1 ) OPCODE(AddWithCarry, T::U32, T::U32, T::U32, T::U1 ) OPCODE(SubWithCarry, T::U32, T::U32, T::U32, T::U1 ) +OPCODE(Add64, T::U64, T::U64, T::U64 ) +OPCODE(Mul, T::U32, T::U32, T::U32 ) +OPCODE(Mul64, T::U64, T::U64, T::U64 ) OPCODE(And, T::U32, T::U32, T::U32 ) OPCODE(Eor, T::U32, T::U32, T::U32 ) OPCODE(Or, T::U32, T::U32, T::U32 ) OPCODE(Not, T::U32, T::U32 ) +OPCODE(SignExtendWordToLong, T::U64, T::U32 ) OPCODE(SignExtendHalfToWord, T::U32, T::U16 ) OPCODE(SignExtendByteToWord, T::U32, T::U8 ) +OPCODE(ZeroExtendWordToLong, T::U64, T::U32 ) OPCODE(ZeroExtendHalfToWord, T::U32, T::U16 ) OPCODE(ZeroExtendByteToWord, T::U32, T::U8 ) OPCODE(ByteReverseWord, T::U32, T::U32 ) diff --git a/src/frontend/translate/translate_arm/data_processing.cpp b/src/frontend/translate/translate_arm/data_processing.cpp index 2356896d..22f8f142 100644 --- a/src/frontend/translate/translate_arm/data_processing.cpp +++ b/src/frontend/translate/translate_arm/data_processing.cpp @@ -614,15 +614,70 @@ bool ArmTranslatorVisitor::arm_ORR_rsr(Cond cond, bool S, Reg n, Reg d, Reg s, S } bool ArmTranslatorVisitor::arm_RSB_imm(Cond cond, bool S, Reg n, Reg d, int rotate, Imm8 imm8) { - return InterpretThisInstruction(); + if (ConditionPassed(cond)) { + u32 imm32 = ArmExpandImm(rotate, imm8); + auto result = ir.SubWithCarry(ir.Imm32(imm32), ir.GetRegister(n), ir.Imm1(1)); + if (d == Reg::PC) { + ASSERT(!S); + ir.ALUWritePC(result.result); + ir.SetTerm(IR::Term::ReturnToDispatch{}); + return false; + } + ir.SetRegister(d, result.result); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(result.result)); + ir.SetZFlag(ir.IsZero(result.result)); + ir.SetCFlag(result.carry); + ir.SetVFlag(result.overflow); + } + } + return true; } bool ArmTranslatorVisitor::arm_RSB_reg(Cond cond, bool S, Reg n, Reg d, Imm5 imm5, ShiftType shift, Reg m) { - return InterpretThisInstruction(); + if (ConditionPassed(cond)) { + auto shifted = EmitImmShift(ir.GetRegister(m), shift, imm5, ir.GetCFlag()); + auto result = ir.SubWithCarry(shifted.result, ir.GetRegister(n), ir.Imm1(1)); + if (d == Reg::PC) { + ASSERT(!S); + ir.ALUWritePC(result.result); + ir.SetTerm(IR::Term::ReturnToDispatch{}); + return false; + } + ir.SetRegister(d, result.result); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(result.result)); + ir.SetZFlag(ir.IsZero(result.result)); + ir.SetCFlag(result.carry); + ir.SetVFlag(result.overflow); + } + } + return true; } bool ArmTranslatorVisitor::arm_RSB_rsr(Cond cond, bool S, Reg n, Reg d, Reg s, ShiftType shift, Reg m) { - return InterpretThisInstruction(); + if (n == Reg::PC || m == Reg::PC || s == Reg::PC) + return UnpredictableInstruction(); + if (ConditionPassed(cond)) { + auto shift_n = ir.LeastSignificantByte(ir.GetRegister(s)); + auto carry_in = ir.GetCFlag(); + auto shifted = EmitRegShift(ir.GetRegister(m), shift, shift_n, carry_in); + auto result = ir.SubWithCarry(shifted.result, ir.GetRegister(n), ir.Imm1(1)); + if (d == Reg::PC) { + ASSERT(!S); + ir.ALUWritePC(result.result); + ir.SetTerm(IR::Term::ReturnToDispatch{}); + return false; + } + ir.SetRegister(d, result.result); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(result.result)); + ir.SetZFlag(ir.IsZero(result.result)); + ir.SetCFlag(result.carry); + ir.SetVFlag(result.overflow); + } + } + return true; } bool ArmTranslatorVisitor::arm_RSC_imm(Cond cond, bool S, Reg n, Reg d, int rotate, Imm8 imm8) { diff --git a/src/frontend/translate/translate_arm/multiply.cpp b/src/frontend/translate/translate_arm/multiply.cpp index 9a2c11d6..4fefcc28 100644 --- a/src/frontend/translate/translate_arm/multiply.cpp +++ b/src/frontend/translate/translate_arm/multiply.cpp @@ -11,33 +11,137 @@ namespace Arm { // Multiply (Normal) instructions bool ArmTranslatorVisitor::arm_MLA(Cond cond, bool S, Reg d, Reg a, Reg m, Reg n) { - return InterpretThisInstruction(); + if (d == Reg::PC || n == Reg::PC || m == Reg::PC) + return UnpredictableInstruction(); + if (ConditionPassed(cond)) { + auto result = ir.Add(ir.Mul(ir.GetRegister(n), ir.GetRegister(m)), ir.GetRegister(a)); + ir.SetRegister(d, result); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(result)); + ir.SetZFlag(ir.IsZero(result)); + } + } + return true; } bool ArmTranslatorVisitor::arm_MUL(Cond cond, bool S, Reg d, Reg m, Reg n) { - return InterpretThisInstruction(); + if (d == Reg::PC || n == Reg::PC || m == Reg::PC) + return UnpredictableInstruction(); + if (ConditionPassed(cond)) { + auto result = ir.Mul(ir.GetRegister(n), ir.GetRegister(m)); + ir.SetRegister(d, result); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(result)); + ir.SetZFlag(ir.IsZero(result)); + } + } + return true; } // Multiply (Long) instructions bool ArmTranslatorVisitor::arm_SMLAL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { - return InterpretThisInstruction(); + if (dLo == Reg::PC || dHi == Reg::PC || n == Reg::PC || m == Reg::PC) + return UnpredictableInstruction(); + if (dLo == dHi) + return UnpredictableInstruction(); + if (ConditionPassed(cond)) { + auto n64 = ir.SignExtendWordToLong(ir.GetRegister(n)); + auto m64 = ir.SignExtendWordToLong(ir.GetRegister(m)); + auto product = ir.Mul64(n64, m64); + auto addend = ir.Pack2x32To1x64(ir.GetRegister(dLo), ir.GetRegister(dHi)); + auto result = ir.Add64(product, addend); + auto lo = ir.LeastSignificantWord(result); + auto hi = ir.MostSignificantWord(result); + ir.SetRegister(dLo, lo); + ir.SetRegister(dHi, hi); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(hi)); + ir.SetZFlag(ir.IsZero64(result)); + } + } + return true; } bool ArmTranslatorVisitor::arm_SMULL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { - return InterpretThisInstruction(); + if (dLo == Reg::PC || dHi == Reg::PC || n == Reg::PC || m == Reg::PC) + return UnpredictableInstruction(); + if (dLo == dHi) + return UnpredictableInstruction(); + if (ConditionPassed(cond)) { + auto n64 = ir.SignExtendWordToLong(ir.GetRegister(n)); + auto m64 = ir.SignExtendWordToLong(ir.GetRegister(m)); + auto result = ir.Mul64(n64, m64); + auto lo = ir.LeastSignificantWord(result); + auto hi = ir.MostSignificantWord(result); + ir.SetRegister(dLo, lo); + ir.SetRegister(dHi, hi); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(hi)); + ir.SetZFlag(ir.IsZero64(result)); + } + } + return true; } bool ArmTranslatorVisitor::arm_UMAAL(Cond cond, Reg dHi, Reg dLo, Reg m, Reg n) { - return InterpretThisInstruction(); + if (dLo == Reg::PC || dHi == Reg::PC || n == Reg::PC || m == Reg::PC) + return UnpredictableInstruction(); + if (dLo == dHi) + return UnpredictableInstruction(); + if (ConditionPassed(cond)) { + auto lo64 = ir.ZeroExtendWordToLong(ir.GetRegister(dLo)); + auto hi64 = ir.ZeroExtendWordToLong(ir.GetRegister(dHi)); + auto n64 = ir.ZeroExtendWordToLong(ir.GetRegister(n)); + auto m64 = ir.ZeroExtendWordToLong(ir.GetRegister(m)); + auto result = ir.Add64(ir.Add64(ir.Mul64(n64, m64), hi64), lo64); + ir.SetRegister(dLo, ir.LeastSignificantWord(result)); + ir.SetRegister(dHi, ir.MostSignificantWord(result)); + } + return true; } bool ArmTranslatorVisitor::arm_UMLAL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { - return InterpretThisInstruction(); + if (dLo == Reg::PC || dHi == Reg::PC || n == Reg::PC || m == Reg::PC) + return UnpredictableInstruction(); + if (dLo == dHi) + return UnpredictableInstruction(); + if (ConditionPassed(cond)) { + auto addend = ir.Pack2x32To1x64(ir.GetRegister(dLo), ir.GetRegister(dHi)); + auto n64 = ir.ZeroExtendWordToLong(ir.GetRegister(n)); + auto m64 = ir.ZeroExtendWordToLong(ir.GetRegister(m)); + auto result = ir.Add64(ir.Mul64(n64, m64), addend); + auto lo = ir.LeastSignificantWord(result); + auto hi = ir.MostSignificantWord(result); + ir.SetRegister(dLo, lo); + ir.SetRegister(dHi, hi); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(hi)); + ir.SetZFlag(ir.IsZero64(result)); + } + } + return true; } bool ArmTranslatorVisitor::arm_UMULL(Cond cond, bool S, Reg dHi, Reg dLo, Reg m, Reg n) { - return InterpretThisInstruction(); + if (dLo == Reg::PC || dHi == Reg::PC || n == Reg::PC || m == Reg::PC) + return UnpredictableInstruction(); + if (dLo == dHi) + return UnpredictableInstruction(); + if (ConditionPassed(cond)) { + auto n64 = ir.ZeroExtendWordToLong(ir.GetRegister(n)); + auto m64 = ir.ZeroExtendWordToLong(ir.GetRegister(m)); + auto result = ir.Mul64(n64, m64); + auto lo = ir.LeastSignificantWord(result); + auto hi = ir.MostSignificantWord(result); + ir.SetRegister(dLo, lo); + ir.SetRegister(dHi, hi); + if (S) { + ir.SetNFlag(ir.MostSignificantBit(hi)); + ir.SetZFlag(ir.IsZero64(result)); + } + } + return true; } diff --git a/src/interface/interface.h b/src/interface/interface.h index 74733b16..b178b0aa 100644 --- a/src/interface/interface.h +++ b/src/interface/interface.h @@ -8,6 +8,7 @@ #include +#include "frontend/arm_types.h" #include "common/common_types.h" namespace Dynarmic { @@ -75,6 +76,8 @@ public: return is_executing; } + std::string Disassemble(Arm::LocationDescriptor descriptor); + private: bool halt_requested = false; bool is_executing = false; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a6d1cc3b..ff5a7f4d 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -33,5 +33,5 @@ set(HEADERS create_directory_groups(${SRCS} ${HEADERS}) add_executable(dynarmic_tests ${SRCS}) -target_link_libraries(dynarmic_tests dynarmic) +target_link_libraries(dynarmic_tests dynarmic ${llvm_libs}) set_target_properties(dynarmic_tests PROPERTIES LINKER_LANGUAGE CXX) diff --git a/tests/arm/fuzz_arm.cpp b/tests/arm/fuzz_arm.cpp index d5aa9248..1435decd 100644 --- a/tests/arm/fuzz_arm.cpp +++ b/tests/arm/fuzz_arm.cpp @@ -7,17 +7,18 @@ #include #include #include -#include +#include #include -#include -#include -#include #include "common/bit_util.h" #include "common/common_types.h" +#include "frontend/arm_types.h" #include "frontend/disassembler/disassembler.h" +#include "frontend/ir/ir.h" +#include "frontend/translate/translate.h" #include "interface/interface.h" +#include "ir_opt/passes.h" #include "rand_int.h" #include "skyeye_interpreter/dyncom/arm_dyncom_interpreter.h" #include "skyeye_interpreter/skyeye_common/armstate.h" @@ -140,12 +141,23 @@ public: } } } - u32 Generate() const { + u32 Generate(bool condition = true) const { u32 inst; do { - u32 random = RandInt(0, 0xFFFF); + u32 random = RandInt(0, 0xFFFFFFFF); + if (condition) + random &= ~(0xF << 28); inst = bits | (random & ~mask); } while (!is_valid(inst)); + + if (condition) { + // Have a one-in-twenty-five chance of actually having a cond. + if (RandInt(1, 25) == 1) + inst |= RandInt(0x0, 0xD) << 28; + else + inst |= 0xE << 28; + } + return inst; } u32 Bits() { return bits; } @@ -219,22 +231,27 @@ void FuzzJitArm(const size_t instruction_count, const size_t instructions_to_exe printf("\nInitial Register Listing: \n"); for (int i = 0; i <= 15; i++) { - printf("%4i: %08x\n", i, initial_regs[i]); + auto reg = Dynarmic::Arm::RegToString(static_cast(i)); + printf("%4s: %08x\n", reg, initial_regs[i]); } printf("\nFinal Register Listing: \n"); printf(" interp jit\n"); for (int i = 0; i <= 15; i++) { - printf("%4i: %08x %08x %s\n", i, interp.Reg[i], jit.Regs()[i], interp.Reg[i] != jit.Regs()[i] ? "*" : ""); + auto reg = Dynarmic::Arm::RegToString(static_cast(i)); + printf("%4s: %08x %08x %s\n", reg, interp.Reg[i], jit.Regs()[i], interp.Reg[i] != jit.Regs()[i] ? "*" : ""); } printf("CPSR: %08x %08x %s\n", interp.Cpsr, jit.Cpsr(), interp.Cpsr != jit.Cpsr() ? "*" : ""); - Dynarmic::IR::Block ir_block = Dynarmic::Arm::Translate({0, false, false, 0}, &MemoryRead32); + Dynarmic::Arm::LocationDescriptor descriptor = {0, false, false, 0}; + Dynarmic::IR::Block ir_block = Dynarmic::Arm::Translate(descriptor, &MemoryRead32); Dynarmic::Optimization::GetSetElimination(ir_block); Dynarmic::Optimization::DeadCodeElimination(ir_block); Dynarmic::Optimization::VerificationPass(ir_block); printf("\n\nIR:\n%s", Dynarmic::IR::DumpBlock(ir_block).c_str()); + printf("\n\nx86_64:\n%s", jit.Disassemble(descriptor).c_str()); + #ifdef _MSC_VER __debugbreak(); #else @@ -444,46 +461,20 @@ TEST_CASE("Fuzz ARM reversal instructions", "[JitX64]") { const std::array rev_instructions = { { - InstructionGenerator("0000011010111111dddd11110011mmmm", is_valid), - InstructionGenerator("0000011010111111dddd11111011mmmm", is_valid), - InstructionGenerator("0000011011111111dddd11111011mmmm", is_valid), + InstructionGenerator("cccc011010111111dddd11110011mmmm", is_valid), + InstructionGenerator("cccc011010111111dddd11111011mmmm", is_valid), + InstructionGenerator("cccc011011111111dddd11111011mmmm", is_valid), } }; - SECTION("REV tests") { + SECTION("Reverse tests") { FuzzJitArm(1, 1, 10000, [&rev_instructions]() -> u32 { - u32 cond = 0xE; - // Have a one-in-twenty-five chance of actually having a cond. - if (RandInt(1, 25) == 1) { - cond = RandInt(0x0, 0xD); - } - return rev_instructions[0].Generate() | (cond << 28); - }); - } - - SECTION("REV16 tests") { - FuzzJitArm(1, 1, 10000, [&rev_instructions]() -> u32 { - u32 cond = 0xE; - // Have a one-in-twenty-five chance of actually having a cond. - if (RandInt(1, 25) == 1) { - cond = RandInt(0x0, 0xD); - } - return rev_instructions[1].Generate() | (cond << 28); - }); - } - - SECTION("REVSH tests") { - FuzzJitArm(1, 1, 10000, [&rev_instructions]() -> u32 { - u32 cond = 0xE; - // Have a one-in-twenty-five chance of actually having a cond. - if (RandInt(1, 25) == 1) { - cond = RandInt(0x0, 0xD); - } - return rev_instructions[2].Generate() | (cond << 28); + return rev_instructions[RandInt(0, rev_instructions.size() - 1)].Generate(); }); } } +/* TEST_CASE("Fuzz ARM Load/Store instructions", "[JitX64]") { auto forbid_r15 = [](u32 inst) -> bool { return Dynarmic::Common::Bits<12, 15>(inst) != 0b1111; @@ -565,68 +556,39 @@ TEST_CASE("Fuzz ARM Load/Store instructions", "[JitX64]") { SECTION("Doubleword tests") { FuzzJitArm(1, 1, 10000, [&doubleword_instructions]() -> u32 { - u32 cond = 0xE; - // Have a one-in-twenty-five chance of actually having a cond. - if (RandInt(1, 25) == 1) { - cond = RandInt(0x0, 0xD); - } - - return doubleword_instructions[RandInt(0, doubleword_instructions.size() - 1)].Generate() | (cond << 28); + return doubleword_instructions[RandInt(0, doubleword_instructions.size() - 1)].Generate(); }); } SECTION("Word tests") { FuzzJitArm(1, 1, 10000, [&word_instructions]() -> u32 { - u32 cond = 0xE; - // Have a one-in-twenty-five chance of actually having a cond. - if (RandInt(1, 25) == 1) { - cond = RandInt(0x0, 0xD); - } - return word_instructions[RandInt(0, word_instructions.size() - 1)].Generate() | (cond << 28); + return word_instructions[RandInt(0, word_instructions.size() - 1)].Generate(); }); } SECTION("Halfword tests") { FuzzJitArm(1, 1, 10000, [&halfword_instructions]() -> u32 { - u32 cond = 0xE; - // Have a one-in-twenty-five chance of actually having a cond. - if (RandInt(1, 25) == 1) { - cond = RandInt(0x0, 0xD); - } - return halfword_instructions[RandInt(0, halfword_instructions.size() - 1)].Generate() | (cond << 28); + return halfword_instructions[RandInt(0, halfword_instructions.size() - 1)].Generate(); }); } SECTION("Byte tests") { FuzzJitArm(1, 1, 10000, [&byte_instructions]() -> u32 { - u32 cond = 0xE; - // Have a one-in-twenty-five chance of actually having a cond. - if (RandInt(1, 25) == 1) { - cond = RandInt(0x0, 0xD); - } - return byte_instructions[RandInt(0, byte_instructions.size() - 1)].Generate() | (cond << 28); + return byte_instructions[RandInt(0, byte_instructions.size() - 1)].Generate(); }); } SECTION("Mixed tests") { FuzzJitArm(10, 10, 10000, [&]() -> u32 { - size_t selection = RandInt(0, 3); - - u32 cond = 0xE; - // Have a one-in-twenty-five chance of actually having a cond. - if (RandInt(1, 25) == 1) { - cond = RandInt(0x0, 0xD); - } - - switch (selection) { + switch (RandInt(0, 3)) { case 0: - return doubleword_instructions[RandInt(0, doubleword_instructions.size() - 1)].Generate() | (cond << 28); + return doubleword_instructions[RandInt(0, doubleword_instructions.size() - 1)].Generate(); case 1: - return word_instructions[RandInt(0, word_instructions.size() - 1)].Generate() | (cond << 28); + return word_instructions[RandInt(0, word_instructions.size() - 1)].Generate(); case 2: - return halfword_instructions[RandInt(0, halfword_instructions.size() - 1)].Generate() | (cond << 28); + return halfword_instructions[RandInt(0, halfword_instructions.size() - 1)].Generate(); case 3: - return byte_instructions[RandInt(0, byte_instructions.size() - 1)].Generate() | (cond << 28); + return byte_instructions[RandInt(0, byte_instructions.size() - 1)].Generate(); } return 0; @@ -638,3 +600,39 @@ TEST_CASE("Fuzz ARM Load/Store instructions", "[JitX64]") { FAIL(); } } +*/ + +TEST_CASE("Fuzz ARM multiply instructions", "[JitX64]") { + auto validate_d_m_n = [](u32 inst) -> bool { + return Dynarmic::Common::Bits<16, 19>(inst) != 15 && + Dynarmic::Common::Bits<8, 11>(inst) != 15 && + Dynarmic::Common::Bits<0, 3>(inst) != 15; + }; + auto validate_d_a_m_n = [&](u32 inst) -> bool { + return validate_d_m_n(inst) && + Dynarmic::Common::Bits<12, 15>(inst) != 15; + }; + auto validate_h_l_m_n = [&](u32 inst) -> bool { + return validate_d_a_m_n(inst) && + Dynarmic::Common::Bits<12, 15>(inst) != Dynarmic::Common::Bits<16, 19>(inst); + }; + + const std::array instructions = { + { + InstructionGenerator("cccc0000001Sddddaaaammmm1001nnnn", validate_d_a_m_n), // MLA + InstructionGenerator("cccc0000000Sdddd0000mmmm1001nnnn", validate_d_m_n), // MUL + + InstructionGenerator("cccc0000111Sddddaaaammmm1001nnnn", validate_h_l_m_n), // SMLAL + InstructionGenerator("cccc0000110Sddddaaaammmm1001nnnn", validate_h_l_m_n), // SMULL + InstructionGenerator("cccc00000100ddddaaaammmm1001nnnn", validate_h_l_m_n), // UMAAL + InstructionGenerator("cccc0000101Sddddaaaammmm1001nnnn", validate_h_l_m_n), // UMLAL + InstructionGenerator("cccc0000100Sddddaaaammmm1001nnnn", validate_h_l_m_n), // UMULL + } + }; + + SECTION("Multiply") { + FuzzJitArm(2, 2, 10000, [&]() -> u32 { + return instructions[RandInt(0, instructions.size() - 1)].Generate(); + }); + } +}